Selaa lähdekoodia

feat(oauth): create struct for generic oauth and add config values

bergquist 9 vuotta sitten
vanhempi
commit
3b6820ef03
7 muutettua tiedostoa jossa 519 lisäystä ja 273 poistoa
  1. 14 1
      conf/defaults.ini
  2. 14 1
      conf/sample.ini
  3. 20 0
      pkg/social/common.go
  4. 205 0
      pkg/social/generic_oauth.go
  5. 213 0
      pkg/social/github_oauth.go
  6. 52 0
      pkg/social/google_oauth.go
  7. 1 271
      pkg/social/social.go

+ 14 - 1
conf/defaults.ini

@@ -59,7 +59,7 @@ cert_key =
 
 #################################### Database ####################################
 [database]
-# You can configure the database connection by specifying type, host, name, user and password 
+# You can configure the database connection by specifying type, host, name, user and password
 # as seperate properties or as on string using the url propertie.
 
 # Either "mysql", "postgres" or "sqlite3", it's your choice
@@ -223,6 +223,19 @@ token_url = https://accounts.google.com/o/oauth2/token
 api_url = https://www.googleapis.com/oauth2/v1/userinfo
 allowed_domains =
 
+#################################### Generic OAuth ##########################
+[auth.generic_oauth]
+enabled = false
+allow_sign_up = false
+client_id = some_id
+client_secret = some_secret
+scopes = user:email
+auth_url =
+token_url =
+api_url =
+team_ids =
+allowed_organizations =
+
 #################################### Basic Auth ##########################
 [auth.basic]
 enabled = true

+ 14 - 1
conf/sample.ini

@@ -61,7 +61,7 @@
 
 #################################### Database ####################################
 [database]
-# You can configure the database connection by specifying type, host, name, user and password 
+# You can configure the database connection by specifying type, host, name, user and password
 # as seperate properties or as on string using the url propertie.
 
 # Either "mysql", "postgres" or "sqlite3", it's your choice
@@ -205,6 +205,19 @@ check_for_updates = true
 ;api_url = https://www.googleapis.com/oauth2/v1/userinfo
 ;allowed_domains =
 
+#################################### Generic OAuth ##########################
+[auth.generic_oauth]
+;enabled = false
+;allow_sign_up = false
+;client_id = some_id
+;client_secret = some_secret
+;scopes = user:email,read:org
+;auth_url = https://foo.bar/login/oauth/authorize
+;token_url = https://foo.bar/login/oauth/access_token
+;api_url = https://foo.bar/user
+;team_ids =
+;allowed_organizations =
+
 #################################### Auth Proxy ##########################
 [auth.proxy]
 ;enabled = false

+ 20 - 0
pkg/social/common.go

@@ -0,0 +1,20 @@
+package social
+
+import (
+	"fmt"
+	"strings"
+)
+
+func isEmailAllowed(email string, allowedDomains []string) bool {
+	if len(allowedDomains) == 0 {
+		return true
+	}
+
+	valid := false
+	for _, domain := range allowedDomains {
+		emailSuffix := fmt.Sprintf("@%s", domain)
+		valid = valid || strings.HasSuffix(email, emailSuffix)
+	}
+
+	return valid
+}

+ 205 - 0
pkg/social/generic_oauth.go

@@ -0,0 +1,205 @@
+package social
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/grafana/grafana/pkg/models"
+
+	"golang.org/x/oauth2"
+)
+
+type GenericOAuth struct {
+	*oauth2.Config
+	allowedDomains       []string
+	allowedOrganizations []string
+	apiUrl               string
+	allowSignup          bool
+	teamIds              []int
+}
+
+func (s *GenericOAuth) Type() int {
+	return int(models.GITHUB)
+}
+
+func (s *GenericOAuth) IsEmailAllowed(email string) bool {
+	return isEmailAllowed(email, s.allowedDomains)
+}
+
+func (s *GenericOAuth) IsSignupAllowed() bool {
+	return s.allowSignup
+}
+
+func (s *GenericOAuth) IsTeamMember(client *http.Client) bool {
+	if len(s.teamIds) == 0 {
+		return true
+	}
+
+	teamMemberships, err := s.FetchTeamMemberships(client)
+	if err != nil {
+		return false
+	}
+
+	for _, teamId := range s.teamIds {
+		for _, membershipId := range teamMemberships {
+			if teamId == membershipId {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func (s *GenericOAuth) IsOrganizationMember(client *http.Client) bool {
+	if len(s.allowedOrganizations) == 0 {
+		return true
+	}
+
+	organizations, err := s.FetchOrganizations(client)
+	if err != nil {
+		return false
+	}
+
+	for _, allowedOrganization := range s.allowedOrganizations {
+		for _, organization := range organizations {
+			if organization == allowedOrganization {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
+	type Record struct {
+		Email    string `json:"email"`
+		Primary  bool   `json:"primary"`
+		Verified bool   `json:"verified"`
+	}
+
+	emailsUrl := fmt.Sprintf(s.apiUrl + "/emails")
+	r, err := client.Get(emailsUrl)
+	if err != nil {
+		return "", err
+	}
+
+	defer r.Body.Close()
+
+	var records []Record
+
+	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
+		return "", err
+	}
+
+	var email = ""
+	for _, record := range records {
+		if record.Primary {
+			email = record.Email
+		}
+	}
+
+	return email, nil
+}
+
+func (s *GenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error) {
+	type Record struct {
+		Id int `json:"id"`
+	}
+
+	membershipUrl := fmt.Sprintf(s.apiUrl + "/teams")
+	r, err := client.Get(membershipUrl)
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	var records []Record
+
+	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
+		return nil, err
+	}
+
+	var ids = make([]int, len(records))
+	for i, record := range records {
+		ids[i] = record.Id
+	}
+
+	return ids, nil
+}
+
+func (s *GenericOAuth) FetchOrganizations(client *http.Client) ([]string, error) {
+	type Record struct {
+		Login string `json:"login"`
+	}
+
+	url := fmt.Sprintf(s.apiUrl + "/orgs")
+	r, err := client.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	var records []Record
+
+	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
+		return nil, err
+	}
+
+	var logins = make([]string, len(records))
+	for i, record := range records {
+		logins[i] = record.Login
+	}
+
+	return logins, nil
+}
+
+func (s *GenericOAuth) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
+	var data struct {
+		Id    int    `json:"id"`
+		Name  string `json:"login"`
+		Email string `json:"email"`
+	}
+
+	var err error
+	client := s.Client(oauth2.NoContext, token)
+	r, err := client.Get(s.apiUrl)
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
+		return nil, err
+	}
+
+	userInfo := &BasicUserInfo{
+		Identity: strconv.Itoa(data.Id),
+		Name:     data.Name,
+		Email:    data.Email,
+	}
+
+	if !s.IsTeamMember(client) {
+		return nil, errors.New("User not a member of one of the required teams")
+	}
+
+	if !s.IsOrganizationMember(client) {
+		return nil, errors.New("User not a member of one of the required organizations")
+	}
+
+	if userInfo.Email == "" {
+		userInfo.Email, err = s.FetchPrivateEmail(client)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return userInfo, nil
+}

+ 213 - 0
pkg/social/github_oauth.go

@@ -0,0 +1,213 @@
+package social
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/grafana/grafana/pkg/models"
+
+	"golang.org/x/oauth2"
+)
+
+type SocialGithub struct {
+	*oauth2.Config
+	allowedDomains       []string
+	allowedOrganizations []string
+	apiUrl               string
+	allowSignup          bool
+	teamIds              []int
+}
+
+var (
+	ErrMissingTeamMembership = errors.New("User not a member of one of the required teams")
+)
+
+var (
+	ErrMissingOrganizationMembership = errors.New("User not a member of one of the required organizations")
+)
+
+func (s *SocialGithub) Type() int {
+	return int(models.GITHUB)
+}
+
+func (s *SocialGithub) IsEmailAllowed(email string) bool {
+	return isEmailAllowed(email, s.allowedDomains)
+}
+
+func (s *SocialGithub) IsSignupAllowed() bool {
+	return s.allowSignup
+}
+
+func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
+	if len(s.teamIds) == 0 {
+		return true
+	}
+
+	teamMemberships, err := s.FetchTeamMemberships(client)
+	if err != nil {
+		return false
+	}
+
+	for _, teamId := range s.teamIds {
+		for _, membershipId := range teamMemberships {
+			if teamId == membershipId {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func (s *SocialGithub) IsOrganizationMember(client *http.Client) bool {
+	if len(s.allowedOrganizations) == 0 {
+		return true
+	}
+
+	organizations, err := s.FetchOrganizations(client)
+	if err != nil {
+		return false
+	}
+
+	for _, allowedOrganization := range s.allowedOrganizations {
+		for _, organization := range organizations {
+			if organization == allowedOrganization {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) {
+	type Record struct {
+		Email    string `json:"email"`
+		Primary  bool   `json:"primary"`
+		Verified bool   `json:"verified"`
+	}
+
+	emailsUrl := fmt.Sprintf(s.apiUrl + "/emails")
+	r, err := client.Get(emailsUrl)
+	if err != nil {
+		return "", err
+	}
+
+	defer r.Body.Close()
+
+	var records []Record
+
+	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
+		return "", err
+	}
+
+	var email = ""
+	for _, record := range records {
+		if record.Primary {
+			email = record.Email
+		}
+	}
+
+	return email, nil
+}
+
+func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) {
+	type Record struct {
+		Id int `json:"id"`
+	}
+
+	membershipUrl := fmt.Sprintf(s.apiUrl + "/teams")
+	r, err := client.Get(membershipUrl)
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	var records []Record
+
+	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
+		return nil, err
+	}
+
+	var ids = make([]int, len(records))
+	for i, record := range records {
+		ids[i] = record.Id
+	}
+
+	return ids, nil
+}
+
+func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) {
+	type Record struct {
+		Login string `json:"login"`
+	}
+
+	url := fmt.Sprintf(s.apiUrl + "/orgs")
+	r, err := client.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	var records []Record
+
+	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
+		return nil, err
+	}
+
+	var logins = make([]string, len(records))
+	for i, record := range records {
+		logins[i] = record.Login
+	}
+
+	return logins, nil
+}
+
+func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
+	var data struct {
+		Id    int    `json:"id"`
+		Name  string `json:"login"`
+		Email string `json:"email"`
+	}
+
+	var err error
+	client := s.Client(oauth2.NoContext, token)
+	r, err := client.Get(s.apiUrl)
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
+		return nil, err
+	}
+
+	userInfo := &BasicUserInfo{
+		Identity: strconv.Itoa(data.Id),
+		Name:     data.Name,
+		Email:    data.Email,
+	}
+
+	if !s.IsTeamMember(client) {
+		return nil, ErrMissingTeamMembership
+	}
+
+	if !s.IsOrganizationMember(client) {
+		return nil, ErrMissingOrganizationMembership
+	}
+
+	if userInfo.Email == "" {
+		userInfo.Email, err = s.FetchPrivateEmail(client)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return userInfo, nil
+}

+ 52 - 0
pkg/social/google_oauth.go

@@ -0,0 +1,52 @@
+package social
+
+import (
+	"encoding/json"
+
+	"github.com/grafana/grafana/pkg/models"
+
+	"golang.org/x/oauth2"
+)
+
+type SocialGoogle struct {
+	*oauth2.Config
+	allowedDomains []string
+	apiUrl         string
+	allowSignup    bool
+}
+
+func (s *SocialGoogle) Type() int {
+	return int(models.GOOGLE)
+}
+
+func (s *SocialGoogle) IsEmailAllowed(email string) bool {
+	return isEmailAllowed(email, s.allowedDomains)
+}
+
+func (s *SocialGoogle) IsSignupAllowed() bool {
+	return s.allowSignup
+}
+
+func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
+	var data struct {
+		Id    string `json:"id"`
+		Name  string `json:"name"`
+		Email string `json:"email"`
+	}
+	var err error
+
+	client := s.Client(oauth2.NoContext, token)
+	r, err := client.Get(s.apiUrl)
+	if err != nil {
+		return nil, err
+	}
+	defer r.Body.Close()
+	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
+		return nil, err
+	}
+	return &BasicUserInfo{
+		Identity: data.Id,
+		Name:     data.Name,
+		Email:    data.Email,
+	}, nil
+}

+ 1 - 271
pkg/social/social.go

@@ -1,14 +1,8 @@
 package social
 
 import (
-	"encoding/json"
-	"errors"
-	"fmt"
-	"net/http"
-	"strconv"
 	"strings"
 
-	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 	"golang.org/x/net/context"
 
@@ -105,7 +99,7 @@ func NewOAuthService() {
 			setting.OAuthService.OAuthProviderName = sec.Key("oauth_provider_name").String()
 			teamIds := sec.Key("team_ids").Ints(",")
 			allowedOrganizations := sec.Key("allowed_organizations").Strings(" ")
-			SocialMap["generic_oauth"] = &SocialGithub{
+			SocialMap["generic_oauth"] = &GenericOAuth{
 				Config:               &config,
 				allowedDomains:       info.AllowedDomains,
 				apiUrl:               info.ApiUrl,
@@ -116,267 +110,3 @@ func NewOAuthService() {
 		}
 	}
 }
-
-func isEmailAllowed(email string, allowedDomains []string) bool {
-	if len(allowedDomains) == 0 {
-		return true
-	}
-
-	valid := false
-	for _, domain := range allowedDomains {
-		emailSuffix := fmt.Sprintf("@%s", domain)
-		valid = valid || strings.HasSuffix(email, emailSuffix)
-	}
-
-	return valid
-}
-
-type SocialGithub struct {
-	*oauth2.Config
-	allowedDomains       []string
-	allowedOrganizations []string
-	apiUrl               string
-	allowSignup          bool
-	teamIds              []int
-}
-
-var (
-	ErrMissingTeamMembership = errors.New("User not a member of one of the required teams")
-)
-
-var (
-	ErrMissingOrganizationMembership = errors.New("User not a member of one of the required organizations")
-)
-
-func (s *SocialGithub) Type() int {
-	return int(models.GITHUB)
-}
-
-func (s *SocialGithub) IsEmailAllowed(email string) bool {
-	return isEmailAllowed(email, s.allowedDomains)
-}
-
-func (s *SocialGithub) IsSignupAllowed() bool {
-	return s.allowSignup
-}
-
-func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
-	if len(s.teamIds) == 0 {
-		return true
-	}
-
-	teamMemberships, err := s.FetchTeamMemberships(client)
-	if err != nil {
-		return false
-	}
-
-	for _, teamId := range s.teamIds {
-		for _, membershipId := range teamMemberships {
-			if teamId == membershipId {
-				return true
-			}
-		}
-	}
-
-	return false
-}
-
-func (s *SocialGithub) IsOrganizationMember(client *http.Client) bool {
-	if len(s.allowedOrganizations) == 0 {
-		return true
-	}
-
-	organizations, err := s.FetchOrganizations(client)
-	if err != nil {
-		return false
-	}
-
-	for _, allowedOrganization := range s.allowedOrganizations {
-		for _, organization := range organizations {
-			if organization == allowedOrganization {
-				return true
-			}
-		}
-	}
-
-	return false
-}
-
-func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) {
-	type Record struct {
-		Email    string `json:"email"`
-		Primary  bool   `json:"primary"`
-		Verified bool   `json:"verified"`
-	}
-
-	emailsUrl := fmt.Sprintf(s.apiUrl + "/emails")
-	r, err := client.Get(emailsUrl)
-	if err != nil {
-		return "", err
-	}
-
-	defer r.Body.Close()
-
-	var records []Record
-
-	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
-		return "", err
-	}
-
-	var email = ""
-	for _, record := range records {
-		if record.Primary {
-			email = record.Email
-		}
-	}
-
-	return email, nil
-}
-
-func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) {
-	type Record struct {
-		Id int `json:"id"`
-	}
-
-	membershipUrl := fmt.Sprintf(s.apiUrl + "/teams")
-	r, err := client.Get(membershipUrl)
-	if err != nil {
-		return nil, err
-	}
-
-	defer r.Body.Close()
-
-	var records []Record
-
-	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
-		return nil, err
-	}
-
-	var ids = make([]int, len(records))
-	for i, record := range records {
-		ids[i] = record.Id
-	}
-
-	return ids, nil
-}
-
-func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) {
-	type Record struct {
-		Login string `json:"login"`
-	}
-
-	url := fmt.Sprintf(s.apiUrl + "/orgs")
-	r, err := client.Get(url)
-	if err != nil {
-		return nil, err
-	}
-
-	defer r.Body.Close()
-
-	var records []Record
-
-	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
-		return nil, err
-	}
-
-	var logins = make([]string, len(records))
-	for i, record := range records {
-		logins[i] = record.Login
-	}
-
-	return logins, nil
-}
-
-func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
-	var data struct {
-		Id    int    `json:"id"`
-		Name  string `json:"login"`
-		Email string `json:"email"`
-	}
-
-	var err error
-	client := s.Client(oauth2.NoContext, token)
-	r, err := client.Get(s.apiUrl)
-	if err != nil {
-		return nil, err
-	}
-
-	defer r.Body.Close()
-
-	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
-		return nil, err
-	}
-
-	userInfo := &BasicUserInfo{
-		Identity: strconv.Itoa(data.Id),
-		Name:     data.Name,
-		Email:    data.Email,
-	}
-
-	if !s.IsTeamMember(client) {
-		return nil, ErrMissingTeamMembership
-	}
-
-	if !s.IsOrganizationMember(client) {
-		return nil, ErrMissingOrganizationMembership
-	}
-
-	if userInfo.Email == "" {
-		userInfo.Email, err = s.FetchPrivateEmail(client)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return userInfo, nil
-}
-
-//   ________                     .__
-//  /  _____/  ____   ____   ____ |  |   ____
-// /   \  ___ /  _ \ /  _ \ / ___\|  | _/ __ \
-// \    \_\  (  <_> |  <_> ) /_/  >  |_\  ___/
-//  \______  /\____/ \____/\___  /|____/\___  >
-//         \/             /_____/           \/
-
-type SocialGoogle struct {
-	*oauth2.Config
-	allowedDomains []string
-	apiUrl         string
-	allowSignup    bool
-}
-
-func (s *SocialGoogle) Type() int {
-	return int(models.GOOGLE)
-}
-
-func (s *SocialGoogle) IsEmailAllowed(email string) bool {
-	return isEmailAllowed(email, s.allowedDomains)
-}
-
-func (s *SocialGoogle) IsSignupAllowed() bool {
-	return s.allowSignup
-}
-
-func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
-	var data struct {
-		Id    string `json:"id"`
-		Name  string `json:"name"`
-		Email string `json:"email"`
-	}
-	var err error
-
-	client := s.Client(oauth2.NoContext, token)
-	r, err := client.Get(s.apiUrl)
-	if err != nil {
-		return nil, err
-	}
-	defer r.Body.Close()
-	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
-		return nil, err
-	}
-	return &BasicUserInfo{
-		Identity: data.Id,
-		Name:     data.Name,
-		Email:    data.Email,
-	}, nil
-}