grafana/pkg/login/social/okta_oauth.go
Jo 00e7324bf6
Auth: Restore legacy behavior and add deprecation notice for empty org role in oauth (#55118)
* Auth: Add deprecation notice for empty org role

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* fix recasts

* fix azure tests missing logger

* Adding test to gitlab oauth

* Covering more cases

* Cover more options

* Add role attributestrict check fail

* Adding one more edge case test

* Using legacy for gitlab

* Yet another edge case YAEC

* Reverting github oauth to legacy

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Not using token

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Nit.

* Adding warning in docs

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* add warning to generic oauth

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Be more precise

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Adding warning to github oauth

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Adding warning to gitlab oauth

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Adding warning to okta oauth

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Add docs about mapping to AzureAD

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Clarify oauth_skip_org_role_update_sync

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Nit.

* Nit on Azure AD

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Reorder docs index

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

* Fix typo

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
Co-authored-by: gamab <gabi.mabs@gmail.com>
2022-09-15 17:35:59 +02:00

146 lines
3.5 KiB
Go

package social
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/models"
"golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2/jwt"
)
type SocialOkta struct {
*SocialBase
apiUrl string
allowedGroups []string
}
type OktaUserInfoJson struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Login string `json:"login"`
Username string `json:"username"`
Email string `json:"email"`
Upn string `json:"upn"`
Attributes map[string][]string `json:"attributes"`
Groups []string `json:"groups"`
rawJSON []byte
}
type OktaClaims struct {
ID string `json:"sub"`
Email string `json:"email"`
PreferredUsername string `json:"preferred_username"`
Name string `json:"name"`
}
func (claims *OktaClaims) extractEmail() string {
if claims.Email == "" && claims.PreferredUsername != "" {
return claims.PreferredUsername
}
return claims.Email
}
func (s *SocialOkta) Type() int {
return int(models.OKTA)
}
func (s *SocialOkta) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
idToken := token.Extra("id_token")
if idToken == nil {
return nil, fmt.Errorf("no id_token found")
}
parsedToken, err := jwt.ParseSigned(idToken.(string))
if err != nil {
return nil, fmt.Errorf("error parsing id token: %w", err)
}
var claims OktaClaims
if err := parsedToken.UnsafeClaimsWithoutVerification(&claims); err != nil {
return nil, fmt.Errorf("error getting claims from id token: %w", err)
}
email := claims.extractEmail()
if email == "" {
return nil, errors.New("error getting user info: no email found in access token")
}
var data OktaUserInfoJson
err = s.extractAPI(&data, client)
if err != nil {
return nil, err
}
groups := s.GetGroups(&data)
if !s.IsGroupMember(groups) {
return nil, errMissingGroupMembership
}
role, grafanaAdmin := s.extractRoleAndAdmin(data.rawJSON, groups, true)
if s.roleAttributeStrict && !role.IsValid() {
return nil, ErrInvalidBasicRole
}
var isGrafanaAdmin *bool = nil
if s.allowAssignGrafanaAdmin {
isGrafanaAdmin = &grafanaAdmin
}
return &BasicUserInfo{
Id: claims.ID,
Name: claims.Name,
Email: email,
Login: email,
Role: role,
IsGrafanaAdmin: isGrafanaAdmin,
Groups: groups,
}, nil
}
func (s *SocialOkta) extractAPI(data *OktaUserInfoJson, client *http.Client) error {
rawUserInfoResponse, err := s.httpGet(client, s.apiUrl)
if err != nil {
s.log.Debug("Error getting user info response", "url", s.apiUrl, "error", err)
return fmt.Errorf("error getting user info response: %w", err)
}
data.rawJSON = rawUserInfoResponse.Body
err = json.Unmarshal(data.rawJSON, data)
if err != nil {
s.log.Debug("Error decoding user info response", "raw_json", data.rawJSON, "error", err)
data.rawJSON = []byte{}
return fmt.Errorf("error decoding user info response: %w", err)
}
s.log.Debug("Received user info response", "raw_json", string(data.rawJSON), "data", data)
return nil
}
func (s *SocialOkta) GetGroups(data *OktaUserInfoJson) []string {
groups := make([]string, 0)
if len(data.Groups) > 0 {
groups = data.Groups
}
return groups
}
func (s *SocialOkta) IsGroupMember(groups []string) bool {
if len(s.allowedGroups) == 0 {
return true
}
for _, allowedGroup := range s.allowedGroups {
for _, group := range groups {
if group == allowedGroup {
return true
}
}
}
return false
}