github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/server/oauth_github.go (about)

     1  package server
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  
     9  	"github.com/sirupsen/logrus"
    10  	"golang.org/x/oauth2"
    11  
    12  	"github.com/pyroscope-io/pyroscope/pkg/config"
    13  )
    14  
    15  type oauthHandlerGithub struct {
    16  	oauthBase
    17  	allowedOrganizations []string
    18  }
    19  
    20  func newGithubHandler(cfg config.GithubOauth, baseURL string, log *logrus.Logger) (*oauthHandlerGithub, error) {
    21  	authURL, err := url.Parse(cfg.AuthURL)
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  
    26  	h := &oauthHandlerGithub{
    27  		oauthBase: oauthBase{
    28  			config: &oauth2.Config{
    29  				ClientID:     cfg.ClientID,
    30  				ClientSecret: cfg.ClientSecret,
    31  				Scopes:       []string{"read:user", "user:email", "read:org"},
    32  				Endpoint:     oauth2.Endpoint{AuthURL: cfg.AuthURL, TokenURL: cfg.TokenURL},
    33  			},
    34  			authURL:       authURL,
    35  			log:           log,
    36  			callbackRoute: "/auth/github/callback",
    37  			redirectRoute: "/auth/github/redirect",
    38  			apiURL:        "https://api.github.com",
    39  			baseURL:       baseURL,
    40  		},
    41  		allowedOrganizations: cfg.AllowedOrganizations,
    42  	}
    43  
    44  	if cfg.RedirectURL != "" {
    45  		h.config.RedirectURL = cfg.RedirectURL
    46  	}
    47  
    48  	return h, nil
    49  }
    50  
    51  type githubOrganizations struct {
    52  	Login string
    53  }
    54  
    55  func (o oauthHandlerGithub) userAuth(client *http.Client) (extUserInfo, error) {
    56  	type userProfileResponse struct {
    57  		ID        int64
    58  		Email     string
    59  		Login     string
    60  		AvatarURL string
    61  	}
    62  
    63  	resp, err := client.Get(o.apiURL + "/user")
    64  	if err != nil {
    65  		return extUserInfo{}, fmt.Errorf("failed to get oauth user info: %w", err)
    66  	}
    67  	defer resp.Body.Close()
    68  
    69  	var userProfile userProfileResponse
    70  	err = json.NewDecoder(resp.Body).Decode(&userProfile)
    71  	if err != nil {
    72  		return extUserInfo{}, fmt.Errorf("failed to decode user profile response: %w", err)
    73  	}
    74  	u := extUserInfo{
    75  		Name:  userProfile.Login,
    76  		Email: userProfile.Email,
    77  	}
    78  
    79  	if len(o.allowedOrganizations) == 0 {
    80  		return u, nil
    81  	}
    82  
    83  	organizations, err := o.fetchOrganizations(client)
    84  	if err != nil {
    85  		return extUserInfo{}, fmt.Errorf("failed to get organizations: %w", err)
    86  	}
    87  
    88  	for _, allowed := range o.allowedOrganizations {
    89  		for _, member := range organizations {
    90  			if member.Login == allowed {
    91  				return u, nil
    92  			}
    93  		}
    94  	}
    95  
    96  	return extUserInfo{}, errForbidden
    97  }
    98  
    99  func (o oauthHandlerGithub) fetchOrganizations(client *http.Client) ([]githubOrganizations, error) {
   100  	orgsURL := o.apiURL + "/user/orgs"
   101  	more := true
   102  	organizations := make([]githubOrganizations, 0)
   103  
   104  	for more {
   105  		resp, err := client.Get(orgsURL)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  		defer resp.Body.Close()
   110  
   111  		var orgs []githubOrganizations
   112  		err = json.NewDecoder(resp.Body).Decode(&orgs)
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  
   117  		organizations = append(organizations, orgs...)
   118  
   119  		orgsURL, more = hasMoreLinkResults(resp.Header)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  	}
   124  
   125  	return organizations, nil
   126  }
   127  
   128  func (o oauthHandlerGithub) getOauthBase() oauthBase {
   129  	return o.oauthBase
   130  }