mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 04:04:00 -06:00
support for decoding JWT id tokens
This commit is contained in:
parent
4720b86f5c
commit
04e17c145f
@ -96,7 +96,9 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
if setting.OAuthService.OAuthInfos[name].TlsClientCert != "" || setting.OAuthService.OAuthInfos[name].TlsClientKey != "" {
|
||||
cert, err := tls.LoadX509KeyPair(setting.OAuthService.OAuthInfos[name].TlsClientCert, setting.OAuthService.OAuthInfos[name].TlsClientKey)
|
||||
if err != nil {
|
||||
log.Fatal(1, "Failed to setup TlsClientCert", "oauth provider", name, "error", err)
|
||||
oauthLogger.Error("Failed to setup TlsClientCert", "oauth provider", name, "error", err)
|
||||
ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCert)", nil)
|
||||
return
|
||||
}
|
||||
|
||||
tr.TLSClientConfig.Certificates = append(tr.TLSClientConfig.Certificates, cert)
|
||||
@ -105,7 +107,9 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
if setting.OAuthService.OAuthInfos[name].TlsClientCa != "" {
|
||||
caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa)
|
||||
if err != nil {
|
||||
log.Fatal(1, "Failed to setup TlsClientCa", "oauth provider", name, "error", err)
|
||||
oauthLogger.Error("Failed to setup TlsClientCa", "oauth provider", name, "error", err)
|
||||
ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCa)", nil)
|
||||
return
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
@ -124,13 +128,13 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
// token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
|
||||
token.TokenType = "Bearer"
|
||||
|
||||
ctx.Logger.Debug("OAuthLogin Got token")
|
||||
oauthLogger.Debug("OAuthLogin Got token", "token", token)
|
||||
|
||||
// set up oauth2 client
|
||||
client := connect.Client(oauthCtx, token)
|
||||
|
||||
// get user info
|
||||
userInfo, err := connect.UserInfo(client)
|
||||
userInfo, err := connect.UserInfo(client, token)
|
||||
if err != nil {
|
||||
if sErr, ok := err.(*social.Error); ok {
|
||||
redirectWithError(ctx, sErr)
|
||||
@ -140,7 +144,7 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Logger.Debug("OAuthLogin got user info", "userInfo", userInfo)
|
||||
oauthLogger.Debug("OAuthLogin got user info", "userInfo", userInfo)
|
||||
|
||||
// validate that we got at least an email address
|
||||
if userInfo.Email == "" {
|
||||
@ -205,7 +209,7 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
}
|
||||
|
||||
func redirectWithError(ctx *middleware.Context, err error, v ...interface{}) {
|
||||
ctx.Logger.Info(err.Error(), v...)
|
||||
oauthLogger.Info(err.Error(), v...)
|
||||
// TODO: we can use the flash storage here once it's implemented
|
||||
ctx.Session.Set("loginError", err.Error())
|
||||
ctx.Redirect(setting.AppSubUrl + "/login")
|
||||
|
@ -1,19 +1,21 @@
|
||||
package social
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GenericOAuth struct {
|
||||
*oauth2.Config
|
||||
type SocialGenericOAuth struct {
|
||||
*SocialBase
|
||||
allowedDomains []string
|
||||
allowedOrganizations []string
|
||||
apiUrl string
|
||||
@ -21,19 +23,19 @@ type GenericOAuth struct {
|
||||
teamIds []int
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) Type() int {
|
||||
func (s *SocialGenericOAuth) Type() int {
|
||||
return int(models.GENERIC)
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) IsEmailAllowed(email string) bool {
|
||||
func (s *SocialGenericOAuth) IsEmailAllowed(email string) bool {
|
||||
return isEmailAllowed(email, s.allowedDomains)
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) IsSignupAllowed() bool {
|
||||
func (s *SocialGenericOAuth) IsSignupAllowed() bool {
|
||||
return s.allowSignup
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) IsTeamMember(client *http.Client) bool {
|
||||
func (s *SocialGenericOAuth) IsTeamMember(client *http.Client) bool {
|
||||
if len(s.teamIds) == 0 {
|
||||
return true
|
||||
}
|
||||
@ -54,7 +56,7 @@ func (s *GenericOAuth) IsTeamMember(client *http.Client) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) IsOrganizationMember(client *http.Client) bool {
|
||||
func (s *SocialGenericOAuth) IsOrganizationMember(client *http.Client) bool {
|
||||
if len(s.allowedOrganizations) == 0 {
|
||||
return true
|
||||
}
|
||||
@ -75,7 +77,7 @@ func (s *GenericOAuth) IsOrganizationMember(client *http.Client) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
|
||||
func (s *SocialGenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
|
||||
type Record struct {
|
||||
Email string `json:"email"`
|
||||
Primary bool `json:"primary"`
|
||||
@ -116,7 +118,7 @@ func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error) {
|
||||
func (s *SocialGenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error) {
|
||||
type Record struct {
|
||||
Id int `json:"id"`
|
||||
}
|
||||
@ -141,7 +143,7 @@ func (s *GenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error)
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) FetchOrganizations(client *http.Client) ([]string, error) {
|
||||
func (s *SocialGenericOAuth) FetchOrganizations(client *http.Client) ([]string, error) {
|
||||
type Record struct {
|
||||
Login string `json:"login"`
|
||||
}
|
||||
@ -176,17 +178,19 @@ type UserInfoJson struct {
|
||||
Attributes map[string][]string `json:"attributes"`
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
||||
func (s *SocialGenericOAuth) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
var data UserInfoJson
|
||||
|
||||
response, err := HttpGet(client, s.apiUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting user info: %s", err)
|
||||
}
|
||||
if s.extractToken(&data, token) != true {
|
||||
response, err := HttpGet(client, s.apiUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting user info: %s", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(response.Body, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting user info: %s", err)
|
||||
err = json.Unmarshal(response.Body, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error decoding user info JSON: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
name, err := s.extractName(data)
|
||||
@ -221,7 +225,37 @@ func (s *GenericOAuth) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
||||
return userInfo, nil
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) extractEmail(data UserInfoJson, client *http.Client) (string, error) {
|
||||
func (s *SocialGenericOAuth) extractToken(data *UserInfoJson, token *oauth2.Token) (bool) {
|
||||
idToken := token.Extra("id_token")
|
||||
if idToken == nil {
|
||||
s.log.Debug("No id_token found", "token", token)
|
||||
return false
|
||||
}
|
||||
|
||||
jwtRegexp := regexp.MustCompile("^([-_a-zA-Z0-9]+)[.]([-_a-zA-Z0-9]+)[.]([-_a-zA-Z0-9]+)$")
|
||||
matched := jwtRegexp.FindStringSubmatch(idToken.(string))
|
||||
if matched == nil {
|
||||
s.log.Debug("id_token is not in JWT format", "id_token", idToken.(string))
|
||||
return false
|
||||
}
|
||||
|
||||
payload, err := base64.RawURLEncoding.DecodeString(matched[2])
|
||||
if err != nil {
|
||||
s.log.Error("Error base64 decoding id_token", "raw_payload", matched[2], "err", err)
|
||||
return false
|
||||
}
|
||||
|
||||
err = json.Unmarshal(payload, data)
|
||||
if err != nil {
|
||||
s.log.Error("Error decoding id_token JSON", "payload", string(payload), "err", err)
|
||||
return false
|
||||
}
|
||||
|
||||
s.log.Debug("Received id_token", "json", string(payload), "data", data)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) extractEmail(data UserInfoJson, client *http.Client) (string, error) {
|
||||
if data.Email != "" {
|
||||
return data.Email, nil
|
||||
}
|
||||
@ -240,7 +274,7 @@ func (s *GenericOAuth) extractEmail(data UserInfoJson, client *http.Client) (str
|
||||
return s.FetchPrivateEmail(client)
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) extractLogin(data UserInfoJson, email string) (string, error) {
|
||||
func (s *SocialGenericOAuth) extractLogin(data UserInfoJson, email string) (string, error) {
|
||||
if data.Login != "" {
|
||||
return data.Login, nil
|
||||
}
|
||||
@ -252,7 +286,7 @@ func (s *GenericOAuth) extractLogin(data UserInfoJson, email string) (string, er
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (s *GenericOAuth) extractName(data UserInfoJson) (string, error) {
|
||||
func (s *SocialGenericOAuth) extractName(data UserInfoJson) (string, error) {
|
||||
if data.Name != "" {
|
||||
return data.Name, nil
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type SocialGithub struct {
|
||||
*oauth2.Config
|
||||
*SocialBase
|
||||
allowedDomains []string
|
||||
allowedOrganizations []string
|
||||
apiUrl string
|
||||
@ -192,7 +192,7 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client, organizationsUrl
|
||||
return logins, nil
|
||||
}
|
||||
|
||||
func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
||||
func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
|
||||
var data struct {
|
||||
Id int `json:"id"`
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type SocialGoogle struct {
|
||||
*oauth2.Config
|
||||
*SocialBase
|
||||
allowedDomains []string
|
||||
hostedDomain string
|
||||
apiUrl string
|
||||
@ -30,7 +30,7 @@ func (s *SocialGoogle) IsSignupAllowed() bool {
|
||||
return s.allowSignup
|
||||
}
|
||||
|
||||
func (s *SocialGoogle) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
||||
func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
var data struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type SocialGrafanaCom struct {
|
||||
*oauth2.Config
|
||||
*SocialBase
|
||||
url string
|
||||
allowedOrganizations []string
|
||||
allowSignup bool
|
||||
@ -49,7 +49,7 @@ func (s *SocialGrafanaCom) IsOrganizationMember(organizations []OrgRecord) bool
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SocialGrafanaCom) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
||||
func (s *SocialGrafanaCom) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
var data struct {
|
||||
Name string `json:"name"`
|
||||
Login string `json:"username"`
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -22,7 +23,7 @@ type BasicUserInfo struct {
|
||||
|
||||
type SocialConnector interface {
|
||||
Type() int
|
||||
UserInfo(client *http.Client) (*BasicUserInfo, error)
|
||||
UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error)
|
||||
IsEmailAllowed(email string) bool
|
||||
IsSignupAllowed() bool
|
||||
|
||||
@ -31,6 +32,11 @@ type SocialConnector interface {
|
||||
Client(ctx context.Context, t *oauth2.Token) *http.Client
|
||||
}
|
||||
|
||||
type SocialBase struct {
|
||||
*oauth2.Config
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
s string
|
||||
}
|
||||
@ -91,10 +97,15 @@ func NewOAuthService() {
|
||||
Scopes: info.Scopes,
|
||||
}
|
||||
|
||||
logger := log.New("oauth.login." + name)
|
||||
|
||||
// GitHub.
|
||||
if name == "github" {
|
||||
SocialMap["github"] = &SocialGithub{
|
||||
Config: &config,
|
||||
SocialBase: &SocialBase{
|
||||
Config: &config,
|
||||
log: logger,
|
||||
},
|
||||
allowedDomains: info.AllowedDomains,
|
||||
apiUrl: info.ApiUrl,
|
||||
allowSignup: info.AllowSignup,
|
||||
@ -106,7 +117,10 @@ func NewOAuthService() {
|
||||
// Google.
|
||||
if name == "google" {
|
||||
SocialMap["google"] = &SocialGoogle{
|
||||
Config: &config,
|
||||
SocialBase: &SocialBase{
|
||||
Config: &config,
|
||||
log: logger,
|
||||
},
|
||||
allowedDomains: info.AllowedDomains,
|
||||
hostedDomain: info.HostedDomain,
|
||||
apiUrl: info.ApiUrl,
|
||||
@ -116,8 +130,11 @@ func NewOAuthService() {
|
||||
|
||||
// Generic - Uses the same scheme as Github.
|
||||
if name == "generic_oauth" {
|
||||
SocialMap["generic_oauth"] = &GenericOAuth{
|
||||
Config: &config,
|
||||
SocialMap["generic_oauth"] = &SocialGenericOAuth{
|
||||
SocialBase: &SocialBase{
|
||||
Config: &config,
|
||||
log: logger,
|
||||
},
|
||||
allowedDomains: info.AllowedDomains,
|
||||
apiUrl: info.ApiUrl,
|
||||
allowSignup: info.AllowSignup,
|
||||
@ -139,7 +156,10 @@ func NewOAuthService() {
|
||||
}
|
||||
|
||||
SocialMap["grafana_com"] = &SocialGrafanaCom{
|
||||
Config: &config,
|
||||
SocialBase: &SocialBase{
|
||||
Config: &config,
|
||||
log: logger,
|
||||
},
|
||||
url: setting.GrafanaComUrl,
|
||||
allowSignup: info.AllowSignup,
|
||||
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
|
||||
|
Loading…
Reference in New Issue
Block a user