2023-12-08 11:20:42 +01:00
|
|
|
package connectors
|
2016-09-07 10:34:56 +02:00
|
|
|
|
|
|
|
import (
|
2020-09-07 05:42:11 -04:00
|
|
|
"bytes"
|
2023-06-14 12:30:40 +00:00
|
|
|
"context"
|
2016-09-07 10:34:56 +02:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2018-01-18 11:24:04 -05:00
|
|
|
"net/mail"
|
2021-09-08 02:57:20 +02:00
|
|
|
"strconv"
|
2016-09-07 10:34:56 +02:00
|
|
|
|
|
|
|
"golang.org/x/oauth2"
|
2023-11-20 09:45:40 +01:00
|
|
|
|
2023-12-08 11:20:42 +01:00
|
|
|
"github.com/grafana/grafana/pkg/login/social"
|
2023-11-20 09:45:40 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2023-12-08 11:20:42 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/ssosettings"
|
|
|
|
ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models"
|
2023-11-20 09:45:40 +01:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
|
|
"github.com/grafana/grafana/pkg/util"
|
2016-09-07 10:34:56 +02:00
|
|
|
)
|
|
|
|
|
2023-12-01 15:35:44 +01:00
|
|
|
const (
|
|
|
|
nameAttributePathKey = "name_attribute_path"
|
|
|
|
loginAttributePathKey = "login_attribute_path"
|
|
|
|
idTokenAttributeNameKey = "id_token_attribute_name" // #nosec G101 not a hardcoded credential
|
|
|
|
)
|
|
|
|
|
|
|
|
var ExtraGenericOAuthSettingKeys = []string{nameAttributePathKey, loginAttributePathKey, idTokenAttributeNameKey, teamIdsKey, allowedOrganizationsKey}
|
2023-11-20 09:45:40 +01:00
|
|
|
|
2023-12-08 11:20:42 +01:00
|
|
|
var _ social.SocialConnector = (*SocialGenericOAuth)(nil)
|
|
|
|
var _ ssosettings.Reloadable = (*SocialGenericOAuth)(nil)
|
|
|
|
|
2018-01-18 17:17:51 -05:00
|
|
|
type SocialGenericOAuth struct {
|
|
|
|
*SocialBase
|
2016-09-07 10:34:56 +02:00
|
|
|
allowedOrganizations []string
|
|
|
|
apiUrl string
|
2021-09-08 02:57:20 +02:00
|
|
|
teamsUrl string
|
2018-09-10 03:45:07 -04:00
|
|
|
emailAttributeName string
|
2019-08-26 12:11:40 -04:00
|
|
|
emailAttributePath string
|
2020-08-03 17:33:27 +03:00
|
|
|
loginAttributePath string
|
2020-10-20 09:56:48 +03:00
|
|
|
nameAttributePath string
|
2021-07-01 22:40:46 +02:00
|
|
|
groupsAttributePath string
|
2020-08-03 17:33:27 +03:00
|
|
|
idTokenAttributeName string
|
2021-09-08 02:57:20 +02:00
|
|
|
teamIdsAttributePath string
|
|
|
|
teamIds []string
|
2023-05-30 11:07:04 +02:00
|
|
|
allowedGroups []string
|
|
|
|
}
|
|
|
|
|
2023-12-08 11:20:42 +01:00
|
|
|
func NewGenericOAuthProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager) *SocialGenericOAuth {
|
|
|
|
config := createOAuthConfig(info, cfg, social.GenericOAuthProviderName)
|
2023-11-20 09:45:40 +01:00
|
|
|
provider := &SocialGenericOAuth{
|
2023-12-15 10:58:08 +01:00
|
|
|
SocialBase: newSocialBase(social.GenericOAuthProviderName, config, info, cfg.AutoAssignOrgRole, *features),
|
2023-11-20 09:45:40 +01:00
|
|
|
apiUrl: info.ApiUrl,
|
|
|
|
teamsUrl: info.TeamsUrl,
|
|
|
|
emailAttributeName: info.EmailAttributeName,
|
|
|
|
emailAttributePath: info.EmailAttributePath,
|
2023-12-01 15:35:44 +01:00
|
|
|
nameAttributePath: info.Extra[nameAttributePathKey],
|
2023-11-20 09:45:40 +01:00
|
|
|
groupsAttributePath: info.GroupsAttributePath,
|
2023-12-01 15:35:44 +01:00
|
|
|
loginAttributePath: info.Extra[loginAttributePathKey],
|
|
|
|
idTokenAttributeName: info.Extra[idTokenAttributeNameKey],
|
2023-11-20 09:45:40 +01:00
|
|
|
teamIdsAttributePath: info.TeamIdsAttributePath,
|
2023-12-01 15:35:44 +01:00
|
|
|
teamIds: util.SplitString(info.Extra[teamIdsKey]),
|
|
|
|
allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]),
|
2023-11-20 09:45:40 +01:00
|
|
|
allowedGroups: info.AllowedGroups,
|
|
|
|
}
|
|
|
|
|
2023-12-08 11:20:42 +01:00
|
|
|
if features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsApi) {
|
|
|
|
ssoSettings.RegisterReloadable(social.GenericOAuthProviderName, provider)
|
|
|
|
}
|
|
|
|
|
|
|
|
return provider
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SocialGenericOAuth) Validate(ctx context.Context, settings ssoModels.SSOSettings) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SocialGenericOAuth) Reload(ctx context.Context, settings ssoModels.SSOSettings) error {
|
|
|
|
return nil
|
2023-11-20 09:45:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODOD: remove this in the next PR and use the isGroupMember from social.go
|
2023-05-30 11:07:04 +02:00
|
|
|
func (s *SocialGenericOAuth) 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
|
2016-09-07 10:34:56 +02:00
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
func (s *SocialGenericOAuth) IsTeamMember(ctx context.Context, client *http.Client) bool {
|
2016-09-07 10:34:56 +02:00
|
|
|
if len(s.teamIds) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
teamMemberships, err := s.FetchTeamMemberships(ctx, client)
|
2021-09-08 02:57:20 +02:00
|
|
|
if err != nil {
|
2016-09-07 10:34:56 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, teamId := range s.teamIds {
|
|
|
|
for _, membershipId := range teamMemberships {
|
|
|
|
if teamId == membershipId {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
func (s *SocialGenericOAuth) IsOrganizationMember(ctx context.Context, client *http.Client) bool {
|
2016-09-07 10:34:56 +02:00
|
|
|
if len(s.allowedOrganizations) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
organizations, ok := s.FetchOrganizations(ctx, client)
|
2020-01-18 04:57:24 +11:00
|
|
|
if !ok {
|
2016-09-07 10:34:56 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, allowedOrganization := range s.allowedOrganizations {
|
|
|
|
for _, organization := range organizations {
|
|
|
|
if organization == allowedOrganization {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-01-18 11:24:04 -05:00
|
|
|
type UserInfoJson struct {
|
2023-04-05 17:24:06 +02:00
|
|
|
Sub string `json:"sub"`
|
2018-01-18 11:24:04 -05:00
|
|
|
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"`
|
2020-01-18 04:57:24 +11:00
|
|
|
rawJSON []byte
|
2020-08-04 18:28:53 +02:00
|
|
|
source string
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
func (info *UserInfoJson) String() string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"Name: %s, Displayname: %s, Login: %s, Username: %s, Email: %s, Upn: %s, Attributes: %v",
|
|
|
|
info.Name, info.DisplayName, info.Login, info.Username, info.Email, info.Upn, info.Attributes)
|
2018-01-18 11:24:04 -05:00
|
|
|
}
|
|
|
|
|
2023-12-08 11:20:42 +01:00
|
|
|
func (s *SocialGenericOAuth) UserInfo(ctx context.Context, client *http.Client, token *oauth2.Token) (*social.BasicUserInfo, error) {
|
2020-08-04 18:28:53 +02:00
|
|
|
s.log.Debug("Getting user info")
|
2022-10-28 23:44:09 +02:00
|
|
|
toCheck := make([]*UserInfoJson, 0, 2)
|
2016-09-07 10:34:56 +02:00
|
|
|
|
2022-10-28 23:44:09 +02:00
|
|
|
if tokenData := s.extractFromToken(token); tokenData != nil {
|
|
|
|
toCheck = append(toCheck, tokenData)
|
|
|
|
}
|
2023-06-14 12:30:40 +00:00
|
|
|
if apiData := s.extractFromAPI(ctx, client); apiData != nil {
|
2022-10-28 23:44:09 +02:00
|
|
|
toCheck = append(toCheck, apiData)
|
|
|
|
}
|
2016-09-07 10:34:56 +02:00
|
|
|
|
2023-12-08 11:20:42 +01:00
|
|
|
userInfo := &social.BasicUserInfo{}
|
2022-10-28 23:44:09 +02:00
|
|
|
for _, data := range toCheck {
|
2020-08-04 18:28:53 +02:00
|
|
|
s.log.Debug("Processing external user info", "source", data.source, "data", data)
|
|
|
|
|
2023-04-05 17:24:06 +02:00
|
|
|
if userInfo.Id == "" {
|
|
|
|
userInfo.Id = data.Sub
|
|
|
|
}
|
|
|
|
|
2020-08-04 18:28:53 +02:00
|
|
|
if userInfo.Name == "" {
|
2020-10-20 09:56:48 +03:00
|
|
|
userInfo.Name = s.extractUserName(data)
|
2020-08-04 18:28:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if userInfo.Login == "" {
|
2023-04-05 17:24:06 +02:00
|
|
|
userInfo.Login = s.extractLogin(data)
|
2020-08-04 18:28:53 +02:00
|
|
|
}
|
2016-09-07 10:34:56 +02:00
|
|
|
|
2020-08-04 18:28:53 +02:00
|
|
|
if userInfo.Email == "" {
|
|
|
|
userInfo.Email = s.extractEmail(data)
|
|
|
|
if userInfo.Email != "" {
|
|
|
|
s.log.Debug("Set user info email from extracted email", "email", userInfo.Email)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-15 10:58:08 +01:00
|
|
|
if userInfo.Role == "" && !s.info.SkipOrgRoleSync {
|
2023-07-17 15:58:16 +02:00
|
|
|
role, grafanaAdmin, err := s.extractRoleAndAdminOptional(data.rawJSON, []string{})
|
|
|
|
if err != nil {
|
|
|
|
s.log.Warn("Failed to extract role", "err", err)
|
|
|
|
} else {
|
|
|
|
userInfo.Role = role
|
|
|
|
if s.allowAssignGrafanaAdmin {
|
|
|
|
userInfo.IsGrafanaAdmin = &grafanaAdmin
|
2022-09-08 12:11:00 +02:00
|
|
|
}
|
2020-08-04 18:28:53 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-01 22:40:46 +02:00
|
|
|
|
2021-11-12 10:22:04 +01:00
|
|
|
if len(userInfo.Groups) == 0 {
|
2021-09-14 02:15:15 +10:00
|
|
|
groups, err := s.extractGroups(data)
|
|
|
|
if err != nil {
|
|
|
|
s.log.Warn("Failed to extract groups", "err", err)
|
|
|
|
} else if len(groups) > 0 {
|
|
|
|
s.log.Debug("Setting user info groups from extracted groups")
|
|
|
|
userInfo.Groups = groups
|
|
|
|
}
|
2021-07-01 22:40:46 +02:00
|
|
|
}
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
2016-09-07 10:34:56 +02:00
|
|
|
|
2023-12-15 10:58:08 +01:00
|
|
|
if userInfo.Role == "" && !s.info.SkipOrgRoleSync {
|
2023-07-17 15:58:16 +02:00
|
|
|
if s.roleAttributeStrict {
|
|
|
|
return nil, errRoleAttributeStrictViolation.Errorf("idP did not return a role attribute")
|
|
|
|
}
|
|
|
|
userInfo.Role = s.defaultRole()
|
|
|
|
}
|
|
|
|
|
2023-12-15 10:58:08 +01:00
|
|
|
if s.allowAssignGrafanaAdmin && s.info.SkipOrgRoleSync {
|
2023-09-04 18:49:47 +02:00
|
|
|
s.log.Debug("AllowAssignGrafanaAdmin and skipOrgRoleSync are both set, Grafana Admin role will not be synced, consider setting one or the other")
|
2022-09-15 17:35:59 +02:00
|
|
|
}
|
|
|
|
|
2020-01-18 04:57:24 +11:00
|
|
|
if userInfo.Email == "" {
|
2020-08-04 18:28:53 +02:00
|
|
|
var err error
|
2023-06-14 12:30:40 +00:00
|
|
|
userInfo.Email, err = s.FetchPrivateEmail(ctx, client)
|
2018-03-06 14:21:36 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-04 18:28:53 +02:00
|
|
|
s.log.Debug("Setting email from fetched private email", "email", userInfo.Email)
|
2017-05-02 08:37:56 -04:00
|
|
|
}
|
|
|
|
|
2020-01-18 04:57:24 +11:00
|
|
|
if userInfo.Login == "" {
|
2020-08-04 18:28:53 +02:00
|
|
|
s.log.Debug("Defaulting to using email for user info login", "email", userInfo.Email)
|
2020-01-18 04:57:24 +11:00
|
|
|
userInfo.Login = userInfo.Email
|
2016-10-11 02:51:44 -04:00
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
if !s.IsTeamMember(ctx, client) {
|
2020-08-04 18:28:53 +02:00
|
|
|
return nil, errors.New("user not a member of one of the required teams")
|
2016-10-11 02:51:44 -04:00
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
if !s.IsOrganizationMember(ctx, client) {
|
2020-08-04 18:28:53 +02:00
|
|
|
return nil, errors.New("user not a member of one of the required organizations")
|
2016-10-11 02:51:44 -04:00
|
|
|
}
|
|
|
|
|
2023-05-30 11:07:04 +02:00
|
|
|
if !s.IsGroupMember(userInfo.Groups) {
|
|
|
|
return nil, errMissingGroupMembership
|
|
|
|
}
|
|
|
|
|
2020-01-18 04:57:24 +11:00
|
|
|
s.log.Debug("User info result", "result", userInfo)
|
2016-09-07 10:34:56 +02:00
|
|
|
return userInfo, nil
|
|
|
|
}
|
2018-01-18 11:24:04 -05:00
|
|
|
|
2023-12-08 11:20:42 +01:00
|
|
|
func (s *SocialGenericOAuth) GetOAuthInfo() *social.OAuthInfo {
|
2023-11-20 09:45:40 +01:00
|
|
|
return s.info
|
|
|
|
}
|
|
|
|
|
2020-08-04 18:28:53 +02:00
|
|
|
func (s *SocialGenericOAuth) extractFromToken(token *oauth2.Token) *UserInfoJson {
|
|
|
|
s.log.Debug("Extracting user info from OAuth token")
|
2020-01-18 04:57:24 +11:00
|
|
|
|
2020-08-03 17:33:27 +03:00
|
|
|
idTokenAttribute := "id_token"
|
|
|
|
if s.idTokenAttributeName != "" {
|
|
|
|
idTokenAttribute = s.idTokenAttributeName
|
|
|
|
s.log.Debug("Using custom id_token attribute name", "attribute_name", idTokenAttribute)
|
|
|
|
}
|
|
|
|
|
|
|
|
idToken := token.Extra(idTokenAttribute)
|
2018-01-18 17:17:51 -05:00
|
|
|
if idToken == nil {
|
|
|
|
s.log.Debug("No id_token found", "token", token)
|
2020-08-04 18:28:53 +02:00
|
|
|
return nil
|
2018-01-18 17:17:51 -05:00
|
|
|
}
|
|
|
|
|
2023-06-14 13:38:16 +00:00
|
|
|
rawJSON, err := s.retrieveRawIDToken(idToken)
|
2020-01-18 04:57:24 +11:00
|
|
|
if err != nil {
|
2023-06-14 13:38:16 +00:00
|
|
|
s.log.Warn("Error retrieving id_token", "error", err, "token", fmt.Sprintf("%+v", token))
|
2020-08-04 18:28:53 +02:00
|
|
|
return nil
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
2020-08-04 18:28:53 +02:00
|
|
|
var data UserInfoJson
|
|
|
|
if err := json.Unmarshal(rawJSON, &data); err != nil {
|
2023-10-17 10:57:35 +02:00
|
|
|
s.log.Error("Error decoding id_token JSON", "raw_json", string(rawJSON), "error", err)
|
2020-08-04 18:28:53 +02:00
|
|
|
return nil
|
2018-01-18 17:17:51 -05:00
|
|
|
}
|
|
|
|
|
2020-08-04 18:28:53 +02:00
|
|
|
data.rawJSON = rawJSON
|
|
|
|
data.source = "token"
|
2020-10-20 09:56:48 +03:00
|
|
|
s.log.Debug("Received id_token", "raw_json", string(data.rawJSON), "data", data.String())
|
2020-08-04 18:28:53 +02:00
|
|
|
return &data
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
func (s *SocialGenericOAuth) extractFromAPI(ctx context.Context, client *http.Client) *UserInfoJson {
|
2020-08-04 18:28:53 +02:00
|
|
|
s.log.Debug("Getting user info from API")
|
2022-10-28 23:44:09 +02:00
|
|
|
if s.apiUrl == "" {
|
|
|
|
s.log.Debug("No api url configured")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
rawUserInfoResponse, err := s.httpGet(ctx, client, s.apiUrl)
|
2018-01-18 17:17:51 -05:00
|
|
|
if err != nil {
|
2020-08-04 18:28:53 +02:00
|
|
|
s.log.Debug("Error getting user info from API", "url", s.apiUrl, "error", err)
|
|
|
|
return nil
|
2018-01-18 17:17:51 -05:00
|
|
|
}
|
|
|
|
|
2020-08-04 18:28:53 +02:00
|
|
|
rawJSON := rawUserInfoResponse.Body
|
|
|
|
|
|
|
|
var data UserInfoJson
|
|
|
|
if err := json.Unmarshal(rawJSON, &data); err != nil {
|
|
|
|
s.log.Error("Error decoding user info response", "raw_json", rawJSON, "error", err)
|
|
|
|
return nil
|
2018-03-06 14:21:36 -05:00
|
|
|
}
|
|
|
|
|
2020-08-04 18:28:53 +02:00
|
|
|
data.rawJSON = rawJSON
|
|
|
|
data.source = "API"
|
2020-10-20 09:56:48 +03:00
|
|
|
s.log.Debug("Received user info response from API", "raw_json", string(rawJSON), "data", data.String())
|
2020-08-04 18:28:53 +02:00
|
|
|
return &data
|
2018-01-18 17:17:51 -05:00
|
|
|
}
|
|
|
|
|
2020-01-18 04:57:24 +11:00
|
|
|
func (s *SocialGenericOAuth) extractEmail(data *UserInfoJson) string {
|
2018-01-18 11:24:04 -05:00
|
|
|
if data.Email != "" {
|
2018-03-06 14:21:36 -05:00
|
|
|
return data.Email
|
2018-01-18 11:24:04 -05:00
|
|
|
}
|
|
|
|
|
2019-08-26 12:11:40 -04:00
|
|
|
if s.emailAttributePath != "" {
|
2021-07-01 22:40:46 +02:00
|
|
|
email, err := s.searchJSONForStringAttr(s.emailAttributePath, data.rawJSON)
|
2020-04-02 17:35:48 +03:00
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Failed to search JSON for attribute", "error", err)
|
|
|
|
} else if email != "" {
|
2019-08-26 12:11:40 -04:00
|
|
|
return email
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-10 03:45:07 -04:00
|
|
|
emails, ok := data.Attributes[s.emailAttributeName]
|
|
|
|
if ok && len(emails) != 0 {
|
|
|
|
return emails[0]
|
2018-01-18 11:24:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if data.Upn != "" {
|
|
|
|
emailAddr, emailErr := mail.ParseAddress(data.Upn)
|
|
|
|
if emailErr == nil {
|
2018-03-06 14:21:36 -05:00
|
|
|
return emailAddr.Address
|
2018-01-18 11:24:04 -05:00
|
|
|
}
|
2020-01-18 04:57:24 +11:00
|
|
|
s.log.Debug("Failed to parse e-mail address", "error", emailErr.Error())
|
2018-01-18 11:24:04 -05:00
|
|
|
}
|
|
|
|
|
2018-03-06 14:21:36 -05:00
|
|
|
return ""
|
2018-01-18 11:24:04 -05:00
|
|
|
}
|
|
|
|
|
2023-04-05 17:24:06 +02:00
|
|
|
func (s *SocialGenericOAuth) extractLogin(data *UserInfoJson) string {
|
|
|
|
if data.Login != "" {
|
|
|
|
s.log.Debug("Setting user info login from login field", "login", data.Login)
|
|
|
|
return data.Login
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.loginAttributePath != "" {
|
|
|
|
s.log.Debug("Searching for login among JSON", "loginAttributePath", s.loginAttributePath)
|
|
|
|
login, err := s.searchJSONForStringAttr(s.loginAttributePath, data.rawJSON)
|
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Failed to search JSON for login attribute", "error", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if login != "" {
|
|
|
|
return login
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.Username != "" {
|
|
|
|
s.log.Debug("Setting user info login from username field", "username", data.Username)
|
|
|
|
return data.Username
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2020-10-20 09:56:48 +03:00
|
|
|
func (s *SocialGenericOAuth) extractUserName(data *UserInfoJson) string {
|
|
|
|
if s.nameAttributePath != "" {
|
2021-07-01 22:40:46 +02:00
|
|
|
name, err := s.searchJSONForStringAttr(s.nameAttributePath, data.rawJSON)
|
2020-10-20 09:56:48 +03:00
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Failed to search JSON for attribute", "error", err)
|
|
|
|
} else if name != "" {
|
|
|
|
s.log.Debug("Setting user info name from nameAttributePath", "nameAttributePath", s.nameAttributePath)
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.Name != "" {
|
|
|
|
s.log.Debug("Setting user info name from name field")
|
|
|
|
return data.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.DisplayName != "" {
|
|
|
|
s.log.Debug("Setting user info name from display name field")
|
|
|
|
return data.DisplayName
|
|
|
|
}
|
|
|
|
|
|
|
|
s.log.Debug("Unable to find user info name")
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2021-07-01 22:40:46 +02:00
|
|
|
func (s *SocialGenericOAuth) extractGroups(data *UserInfoJson) ([]string, error) {
|
|
|
|
if s.groupsAttributePath == "" {
|
|
|
|
return []string{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.searchJSONForStringArrayAttr(s.groupsAttributePath, data.rawJSON)
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
func (s *SocialGenericOAuth) FetchPrivateEmail(ctx context.Context, client *http.Client) (string, error) {
|
2020-01-18 04:57:24 +11:00
|
|
|
type Record struct {
|
|
|
|
Email string `json:"email"`
|
|
|
|
Primary bool `json:"primary"`
|
|
|
|
IsPrimary bool `json:"is_primary"`
|
|
|
|
Verified bool `json:"verified"`
|
|
|
|
IsConfirmed bool `json:"is_confirmed"`
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
response, err := s.httpGet(ctx, client, fmt.Sprintf(s.apiUrl+"/emails"))
|
2020-01-18 04:57:24 +11:00
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Error getting email address", "url", s.apiUrl+"/emails", "error", err)
|
2022-06-03 03:24:24 -04:00
|
|
|
return "", fmt.Errorf("%v: %w", "Error getting email address", err)
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
var records []Record
|
|
|
|
|
|
|
|
err = json.Unmarshal(response.Body, &records)
|
|
|
|
if err != nil {
|
|
|
|
var data struct {
|
|
|
|
Values []Record `json:"values"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(response.Body, &data)
|
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Error decoding email addresses response", "raw_json", string(response.Body), "error", err)
|
2022-06-03 03:24:24 -04:00
|
|
|
return "", fmt.Errorf("%v: %w", "Error decoding email addresses response", err)
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
records = data.Values
|
|
|
|
}
|
|
|
|
|
|
|
|
s.log.Debug("Received email addresses", "emails", records)
|
|
|
|
|
|
|
|
var email = ""
|
|
|
|
for _, record := range records {
|
|
|
|
if record.Primary || record.IsPrimary {
|
|
|
|
email = record.Email
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s.log.Debug("Using email address", "email", email)
|
|
|
|
|
|
|
|
return email, nil
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
func (s *SocialGenericOAuth) FetchTeamMemberships(ctx context.Context, client *http.Client) ([]string, error) {
|
2021-09-08 02:57:20 +02:00
|
|
|
var err error
|
|
|
|
var ids []string
|
|
|
|
|
|
|
|
if s.teamsUrl == "" {
|
2023-06-14 12:30:40 +00:00
|
|
|
ids, err = s.fetchTeamMembershipsFromDeprecatedTeamsUrl(ctx, client)
|
2021-09-08 02:57:20 +02:00
|
|
|
} else {
|
2023-06-14 12:30:40 +00:00
|
|
|
ids, err = s.fetchTeamMembershipsFromTeamsUrl(ctx, client)
|
2021-09-08 02:57:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
s.log.Debug("Received team memberships", "ids", ids)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ids, err
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
func (s *SocialGenericOAuth) fetchTeamMembershipsFromDeprecatedTeamsUrl(ctx context.Context, client *http.Client) ([]string, error) {
|
2021-09-08 02:57:20 +02:00
|
|
|
var ids []string
|
|
|
|
|
2020-01-18 04:57:24 +11:00
|
|
|
type Record struct {
|
|
|
|
Id int `json:"id"`
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
response, err := s.httpGet(ctx, client, fmt.Sprintf(s.apiUrl+"/teams"))
|
2020-01-18 04:57:24 +11:00
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Error getting team memberships", "url", s.apiUrl+"/teams", "error", err)
|
2021-09-08 02:57:20 +02:00
|
|
|
return []string{}, err
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
var records []Record
|
|
|
|
|
|
|
|
err = json.Unmarshal(response.Body, &records)
|
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Error decoding team memberships response", "raw_json", string(response.Body), "error", err)
|
2021-09-08 02:57:20 +02:00
|
|
|
return []string{}, err
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
2021-09-08 02:57:20 +02:00
|
|
|
ids = make([]string, len(records))
|
2020-01-18 04:57:24 +11:00
|
|
|
for i, record := range records {
|
2021-09-08 02:57:20 +02:00
|
|
|
ids[i] = strconv.Itoa(record.Id)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ids, nil
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
func (s *SocialGenericOAuth) fetchTeamMembershipsFromTeamsUrl(ctx context.Context, client *http.Client) ([]string, error) {
|
2021-09-08 02:57:20 +02:00
|
|
|
if s.teamIdsAttributePath == "" {
|
|
|
|
return []string{}, nil
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
response, err := s.httpGet(ctx, client, fmt.Sprintf(s.teamsUrl))
|
2021-09-08 02:57:20 +02:00
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Error getting team memberships", "url", s.teamsUrl, "error", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-18 04:57:24 +11:00
|
|
|
|
2021-09-08 02:57:20 +02:00
|
|
|
return s.searchJSONForStringArrayAttr(s.teamIdsAttributePath, response.Body)
|
2020-01-18 04:57:24 +11:00
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
func (s *SocialGenericOAuth) FetchOrganizations(ctx context.Context, client *http.Client) ([]string, bool) {
|
2020-01-18 04:57:24 +11:00
|
|
|
type Record struct {
|
|
|
|
Login string `json:"login"`
|
|
|
|
}
|
|
|
|
|
2023-06-14 12:30:40 +00:00
|
|
|
response, err := s.httpGet(ctx, client, fmt.Sprintf(s.apiUrl+"/orgs"))
|
2020-01-18 04:57:24 +11:00
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Error getting organizations", "url", s.apiUrl+"/orgs", "error", err)
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
var records []Record
|
|
|
|
|
|
|
|
err = json.Unmarshal(response.Body, &records)
|
|
|
|
if err != nil {
|
|
|
|
s.log.Error("Error decoding organization response", "response", string(response.Body), "error", err)
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
var logins = make([]string, len(records))
|
|
|
|
for i, record := range records {
|
|
|
|
logins[i] = record.Login
|
|
|
|
}
|
|
|
|
|
|
|
|
s.log.Debug("Received organizations", "logins", logins)
|
|
|
|
|
|
|
|
return logins, true
|
|
|
|
}
|
2022-11-18 10:12:17 +01:00
|
|
|
|
2023-03-16 07:46:25 +00:00
|
|
|
func (s *SocialGenericOAuth) SupportBundleContent(bf *bytes.Buffer) error {
|
|
|
|
bf.WriteString("## GenericOAuth specific configuration\n\n")
|
|
|
|
bf.WriteString("```ini\n")
|
|
|
|
bf.WriteString(fmt.Sprintf("name_attribute_path = %s\n", s.nameAttributePath))
|
|
|
|
bf.WriteString(fmt.Sprintf("login_attribute_path = %s\n", s.loginAttributePath))
|
|
|
|
bf.WriteString(fmt.Sprintf("id_token_attribute_name = %s\n", s.idTokenAttributeName))
|
|
|
|
bf.WriteString(fmt.Sprintf("team_ids_attribute_path = %s\n", s.teamIdsAttributePath))
|
|
|
|
bf.WriteString(fmt.Sprintf("team_ids = %v\n", s.teamIds))
|
|
|
|
bf.WriteString(fmt.Sprintf("allowed_organizations = %v\n", s.allowedOrganizations))
|
|
|
|
bf.WriteString("```\n\n")
|
|
|
|
|
|
|
|
return s.SocialBase.SupportBundleContent(bf)
|
|
|
|
}
|