package github import ( "context" "encoding/json" "fmt" "io/ioutil" "net/http" "os" "time" "github.com/vx3r/wg-gen-web/model" "golang.org/x/oauth2" oauth2Github "golang.org/x/oauth2/github" ) // Github in order to implement interface, struct is required type Github struct{} var ( oauth2Config *oauth2.Config ) // Setup validate provider func (o *Github) Setup() error { oauth2Config = &oauth2.Config{ ClientID: os.Getenv("OAUTH2_CLIENT_ID"), ClientSecret: os.Getenv("OAUTH2_CLIENT_SECRET"), RedirectURL: os.Getenv("OAUTH2_REDIRECT_URL"), Scopes: []string{"read:user", "user:email", "read:org"}, Endpoint: oauth2Github.Endpoint, } return nil } // Check if current user is in given org func (o *Github) CheckMembership(oauth2Token *oauth2.Token, org string, teams []string) (bool, error) { // we have the token, lets get user information user, err := o.UserInfo(oauth2Token) if err != nil { return false, err } client := &http.Client{} // If teams is empty, check for org membership if len(teams) == 0 { url := fmt.Sprintf("https://api.github.com/orgs/%s/members/%s", org, user.Name) req, err := http.NewRequest("GET", url, nil) if err != nil { return false, err } req.Header.Set("Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken)) resp, err := client.Do(req) if err == nil && resp.StatusCode == 204 { return true, nil } return false, err } // If team slice is not empty, check for team membership // GET /orgs/{org}/teams/{team_slug}/memberships/{username} for _, team := range teams { url := fmt.Sprintf("https://api.github.com/orgs/%s/teams/%s/memberships/%s", org, team, user.Name) req, err := http.NewRequest("GET", url, nil) if err != nil { return false, err } req.Header.Set("Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken)) resp, err := client.Do(req) if err == nil && resp.StatusCode == 200 { return true, nil } } return false, err } // CodeUrl get url to redirect client for auth func (o *Github) CodeUrl(state string) string { return oauth2Config.AuthCodeURL(state) } // Exchange exchange code for Oauth2 token func (o *Github) Exchange(code string) (*oauth2.Token, error) { oauth2Token, err := oauth2Config.Exchange(context.TODO(), code) if err != nil { return nil, err } return oauth2Token, nil } // UserInfo get token user func (o *Github) UserInfo(oauth2Token *oauth2.Token) (*model.User, error) { // https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/ // we have the token, lets get user information req, err := http.NewRequest("GET", "https://api.github.com/user", nil) if err != nil { return nil, err } req.Header.Set("Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken)) client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } if resp.StatusCode != 200 { return nil, fmt.Errorf("http status %s expect 200 OK", resp.Status) } bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } defer resp.Body.Close() var data map[string]interface{} err = json.Unmarshal(bodyBytes, &data) if err != nil { return nil, err } // get some infos about user user := &model.User{} if val, ok := data["login"]; ok && val != nil { user.Name = val.(string) } if val, ok := data["email"]; ok && val != nil { user.Email = val.(string) } // openid specific user.Sub = "github is not an openid provider" user.Issuer = "https://github.com" user.IssuedAt = time.Now().UTC() return user, nil }