Auth: Add org to role mappings support to AzureAD/Entra integration (#88861)

* Added implementation and tests

* Add docs, simplify implementation

* Remove unused func

* Update docs
This commit is contained in:
Misi 2024-06-10 12:08:30 +02:00 committed by GitHub
parent ee75fc3852
commit 9a44296bc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 294 additions and 159 deletions

View File

@ -756,6 +756,7 @@ allowed_domains =
allowed_groups = allowed_groups =
allowed_organizations = allowed_organizations =
role_attribute_strict = false role_attribute_strict = false
org_mapping =
allow_assign_grafana_admin = false allow_assign_grafana_admin = false
force_use_graph_api = false force_use_graph_api = false
tls_skip_verify_insecure = false tls_skip_verify_insecure = false

View File

@ -694,6 +694,7 @@
;allowed_groups = ;allowed_groups =
;allowed_organizations = ;allowed_organizations =
;role_attribute_strict = false ;role_attribute_strict = false
;org_mapping =
;allow_assign_grafana_admin = false ;allow_assign_grafana_admin = false
;use_pkce = true ;use_pkce = true
# prevent synchronizing users organization roles # prevent synchronizing users organization roles

View File

@ -411,12 +411,27 @@ By default, Azure AD authentication will map users to organization roles based o
If no application role is found, the user is assigned the role specified by If no application role is found, the user is assigned the role specified by
[the `auto_assign_org_role` option]({{< relref "../../../configure-grafana#auto_assign_org_role" >}}). [the `auto_assign_org_role` option]({{< relref "../../../configure-grafana#auto_assign_org_role" >}}).
You can disable this default role assignment by setting `role_attribute_strict = true`. You can disable this default role assignment by setting `role_attribute_strict = true`. This setting denies user access if no role or an invalid role is returned and the `org_mapping` expression evaluates to an empty mapping.
It denies user access if no role or an invalid role is returned.
You can use the `org_mapping` configuration option to assign the user to multiple organizations and specify their role based on their Entra ID group membership. For more information, refer to [Org roles mapping example](#org-roles-mapping-example). If the org role mapping (`org_mapping`) is specified and Entra ID returns a valid role, then the user will get the highest of the two roles.
**On every login** the user organization role will be reset to match Entra ID's application role and **On every login** the user organization role will be reset to match Entra ID's application role and
their organization membership will be reset to the default organization. their organization membership will be reset to the default organization.
#### Org roles mapping example
The Entra ID integration uses the external users' groups in the `org_mapping` configuration to map organizations and roles based on their Entra ID group membership.
In this example, the user has been granted the role of a `Viewer` in the `org_foo` organization, and the role of an `Editor` in the `org_bar` and `org_baz` orgs.
The external user is part of the following Entra ID groups: `032cb8e0-240f-4347-9120-6f33013e817a` and `bce1c492-0679-4989-941b-8de5e6789cb9`.
Config:
```ini
org_mapping = ["032cb8e0-240f-4347-9120-6f33013e817a:org_foo:Viewer", "bce1c492-0679-4989-941b-8de5e6789cb9:org_bar:Editor", "*:org_baz:Editor"]
```
## Skip organization role sync ## Skip organization role sync
If Azure AD authentication is not intended to sync user roles and organization membership and prevent the sync of org roles from Entra ID, set `skip_org_role_sync` to `true`. This is useful if you want to manage the organization roles for your users from within Grafana or that your organization roles are synced from another provider. If Azure AD authentication is not intended to sync user roles and organization membership and prevent the sync of org roles from Entra ID, set `skip_org_role_sync` to `true`. This is useful if you want to manage the organization roles for your users from within Grafana or that your organization roles are synced from another provider.

View File

@ -148,6 +148,8 @@ The user's role is retrieved using a [JMESPath](http://jmespath.org/examples.htm
To map the server administrator role, use the `allow_assign_grafana_admin` configuration option. To map the server administrator role, use the `allow_assign_grafana_admin` configuration option.
Refer to [configuration options]({{< relref "#configuration-options" >}}) for more information. Refer to [configuration options]({{< relref "#configuration-options" >}}) for more information.
You can use the `org_mapping` configuration option to assign the user to multiple organizations and specify their role based on their GitLab group membership. For more information, refer to [Org roles mapping example](#org-roles-mapping-example). If the org role mapping (`org_mapping`) is specified and Entra ID returns a valid role, then the user will get the highest of the two roles.
If no valid role is found, the user is assigned the role specified by [the `auto_assign_org_role` option]({{< relref "../../../configure-grafana#auto_assign_org_role" >}}). If no valid role is found, the user is assigned the role specified by [the `auto_assign_org_role` option]({{< relref "../../../configure-grafana#auto_assign_org_role" >}}).
You can disable this default role assignment by setting `role_attribute_strict = true`. This setting denies user access if no role or an invalid role is returned after evaluating the `role_attribute_path` and the `org_mapping` expressions. You can disable this default role assignment by setting `role_attribute_strict = true`. This setting denies user access if no role or an invalid role is returned after evaluating the `role_attribute_path` and the `org_mapping` expressions.

View File

@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -121,26 +120,42 @@ func (s *SocialAzureAD) UserInfo(ctx context.Context, client *http.Client, token
return nil, ErrEmailNotFound return nil, ErrEmailNotFound
} }
// setting the role, grafanaAdmin to empty to reflect that we are not syncronizing with the external provider
var role roletype.RoleType
var grafanaAdmin bool
if !s.info.SkipOrgRoleSync {
role, grafanaAdmin, err = s.extractRoleAndAdmin(claims)
if err != nil {
return nil, err
}
if !role.IsValid() {
return nil, errInvalidRole.Errorf("AzureAD OAuth: invalid role %q", role)
}
}
s.log.Debug("AzureAD OAuth: extracted role", "email", email, "role", role)
groups, err := s.extractGroups(ctx, client, claims, token) groups, err := s.extractGroups(ctx, client, claims, token)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to extract groups: %w", err) return nil, fmt.Errorf("failed to extract groups: %w", err)
} }
s.log.Debug("AzureAD OAuth: extracted groups", "email", email, "groups", fmt.Sprintf("%v", groups)) s.log.Debug("AzureAD OAuth: extracted groups", "email", email, "groups", fmt.Sprintf("%v", groups))
userInfo := &social.BasicUserInfo{
Id: claims.ID,
Name: claims.Name,
Email: email,
Login: email,
Groups: groups,
}
if !s.info.SkipOrgRoleSync {
directlyMappedRole, grafanaAdmin := s.extractRoleAndAdminOptional(claims)
s.log.Debug("AzureAD OAuth: extracted role", "email", email, "role", directlyMappedRole)
if s.info.AllowAssignGrafanaAdmin {
userInfo.IsGrafanaAdmin = &grafanaAdmin
}
userInfo.OrgRoles = s.orgRoleMapper.MapOrgRoles(s.orgMappingCfg, userInfo.Groups, directlyMappedRole)
if s.info.RoleAttributeStrict && len(userInfo.OrgRoles) == 0 {
return nil, errRoleAttributeStrictViolation.Errorf("could not evaluate any valid roles using IdP provided data")
}
s.log.Debug("AzureAD OAuth: mapped org roles", "email", email, "roles", fmt.Sprintf("%v", userInfo.OrgRoles))
}
if s.info.AllowAssignGrafanaAdmin && s.info.SkipOrgRoleSync {
s.log.Debug("AllowAssignGrafanaAdmin and skipOrgRoleSync are both set, Grafana Admin role will not be synced, consider setting one or the other")
}
if !s.isGroupMember(groups) { if !s.isGroupMember(groups) {
if len(groups) == 0 { if len(groups) == 0 {
// either they do not have a group or misconfiguration // either they do not have a group or misconfiguration
@ -150,24 +165,7 @@ func (s *SocialAzureAD) UserInfo(ctx context.Context, client *http.Client, token
return nil, errMissingGroupMembership return nil, errMissingGroupMembership
} }
var isGrafanaAdmin *bool = nil return userInfo, nil
if s.info.AllowAssignGrafanaAdmin {
isGrafanaAdmin = &grafanaAdmin
}
if s.info.AllowAssignGrafanaAdmin && s.info.SkipOrgRoleSync {
s.log.Debug("AllowAssignGrafanaAdmin and skipOrgRoleSync are both set, Grafana Admin role will not be synced, consider setting one or the other")
}
return &social.BasicUserInfo{
Id: claims.ID,
Name: claims.Name,
Email: email,
Login: email,
Role: role,
IsGrafanaAdmin: isGrafanaAdmin,
Groups: groups,
}, nil
} }
func (s *SocialAzureAD) Reload(ctx context.Context, settings ssoModels.SSOSettings) error { func (s *SocialAzureAD) Reload(ctx context.Context, settings ssoModels.SSOSettings) error {
@ -289,12 +287,9 @@ func (claims *azureClaims) extractEmail() string {
} }
// extractRoleAndAdmin extracts the role from the claims and returns the role and whether the user is a Grafana admin. // extractRoleAndAdmin extracts the role from the claims and returns the role and whether the user is a Grafana admin.
func (s *SocialAzureAD) extractRoleAndAdmin(claims *azureClaims) (org.RoleType, bool, error) { func (s *SocialAzureAD) extractRoleAndAdminOptional(claims *azureClaims) (org.RoleType, bool) {
if len(claims.Roles) == 0 { if len(claims.Roles) == 0 {
if s.info.RoleAttributeStrict { return "", false
return "", false, errRoleAttributeStrictViolation.Errorf("AzureAD OAuth: unset role")
}
return s.defaultRole(), false, nil
} }
roleOrder := []org.RoleType{social.RoleGrafanaAdmin, org.RoleAdmin, org.RoleEditor, roleOrder := []org.RoleType{social.RoleGrafanaAdmin, org.RoleAdmin, org.RoleEditor,
@ -302,18 +297,14 @@ func (s *SocialAzureAD) extractRoleAndAdmin(claims *azureClaims) (org.RoleType,
for _, role := range roleOrder { for _, role := range roleOrder {
if found := hasRole(claims.Roles, role); found { if found := hasRole(claims.Roles, role); found {
if role == social.RoleGrafanaAdmin { if role == social.RoleGrafanaAdmin {
return org.RoleAdmin, true, nil return org.RoleAdmin, true
} }
return role, false, nil return role, false
} }
} }
if s.info.RoleAttributeStrict { return "", false
return "", false, errRoleAttributeStrictViolation.Errorf("AzureAD OAuth: idP did not return a valid role %q", claims.Roles)
}
return s.defaultRole(), false, nil
} }
func hasRole(roles []string, role org.RoleType) bool { func hasRole(roles []string, role org.RoleType) bool {

View File

@ -20,6 +20,8 @@ import (
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/ssosettings" "github.com/grafana/grafana/pkg/services/ssosettings"
ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models" ssoModels "github.com/grafana/grafana/pkg/services/ssosettings/models"
"github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests"
@ -75,12 +77,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
}, },
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Viewer", OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
@ -139,12 +141,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
usGovURL: true, usGovURL: true,
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Viewer", OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
@ -166,12 +168,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
}, },
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Viewer", OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
@ -193,12 +195,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
ID: "1234", ID: "1234",
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Admin", OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
@ -220,12 +222,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
ID: "1234", ID: "1234",
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Admin", OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
@ -247,15 +249,14 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
ID: "1234", ID: "1234",
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Viewer", OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{}, Groups: []string{},
}, },
}, },
// TODO: @mgyongyosi check this test
{ {
name: "role from env variable", name: "role from env variable",
claims: &azureClaims{ claims: &azureClaims{
@ -275,12 +276,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
}, },
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Editor", OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
@ -302,12 +303,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
}, },
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Editor", OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
@ -329,12 +330,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
ID: "1234", ID: "1234",
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Admin", OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
@ -362,7 +363,7 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Admin", OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
Groups: []string{}, Groups: []string{},
IsGrafanaAdmin: nil, IsGrafanaAdmin: nil,
}, },
@ -391,7 +392,7 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Editor", OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
Groups: []string{}, Groups: []string{},
IsGrafanaAdmin: falseBoolPtr(), IsGrafanaAdmin: falseBoolPtr(),
}, },
@ -420,7 +421,7 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Admin", OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
Groups: []string{}, Groups: []string{},
IsGrafanaAdmin: trueBoolPtr(), IsGrafanaAdmin: trueBoolPtr(),
}, },
@ -500,12 +501,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
ID: "1234", ID: "1234",
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Viewer", OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{"foo", "bar"}, Groups: []string{"foo", "bar"},
}, },
wantErr: false, wantErr: false,
}, },
@ -531,12 +532,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
ID: "1234", ID: "1234",
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Viewer", OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{"foo"}, Groups: []string{"foo"},
}, },
}, },
{ {
@ -585,12 +586,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
}, },
settingAutoAssignOrgRole: "", settingAutoAssignOrgRole: "",
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1", Id: "1",
Name: "test", Name: "test",
Email: "test@test.com", Email: "test@test.com",
Login: "test@test.com", Login: "test@test.com",
Role: "Viewer", OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{"from_server"}, Groups: []string{"from_server"},
}, },
wantErr: false, wantErr: false,
}, },
@ -620,12 +621,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
}, },
settingAutoAssignOrgRole: "", settingAutoAssignOrgRole: "",
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1", Id: "1",
Name: "test", Name: "test",
Email: "test@test.com", Email: "test@test.com",
Login: "test@test.com", Login: "test@test.com",
Role: "Viewer", OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{"from_server"}, Groups: []string{"from_server"},
}, },
wantErr: false, wantErr: false,
}, },
@ -675,6 +676,131 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
{
name: "should map role when org mapping is set, IdP returns with invalid role and role attribute strict is enabled",
fields: fields{
providerCfg: &social.OAuthInfo{
Name: "azuread",
ClientId: "client-id-example",
RoleAttributeStrict: true,
OrgMapping: []string{"group1:Org4:Editor", "*:5:Viewer"},
},
cfg: &setting.Cfg{},
},
claims: &azureClaims{
PreferredUsername: "",
Roles: []string{"Invalid"},
Groups: []string{"group1", "group3"},
Name: "My Name",
ID: "1234",
Email: "me@example.com",
},
want: &social.BasicUserInfo{
Id: "1234",
Name: "My Name",
Email: "me@example.com",
Login: "me@example.com",
OrgRoles: map[int64]org.RoleType{4: org.RoleEditor, 5: org.RoleViewer},
Groups: []string{"group1", "group3"},
},
},
{
name: "should map role when org mapping is set and IdP returns with empty role list",
fields: fields{
providerCfg: &social.OAuthInfo{
Name: "azuread",
ClientId: "client-id-example",
OrgMapping: []string{"group1:Org4:Editor", "group2:5:Viewer"},
},
cfg: &setting.Cfg{
AutoAssignOrgRole: "Viewer",
},
},
claims: &azureClaims{
Email: "me@example.com",
PreferredUsername: "",
Roles: []string{},
Groups: []string{"group1"},
Name: "My Name",
ID: "1234",
},
want: &social.BasicUserInfo{
Id: "1234",
Name: "My Name",
Email: "me@example.com",
Login: "me@example.com",
OrgRoles: map[int64]org.RoleType{4: org.RoleEditor},
Groups: []string{"group1"},
},
},
{
name: "should map role when only org mapping is set and role attribute strict is enabled",
fields: fields{
providerCfg: &social.OAuthInfo{
Name: "azuread",
ClientId: "client-id-example",
RoleAttributeStrict: true,
OrgMapping: []string{"group1:Org4:Editor", "*:5:Viewer"},
},
cfg: &setting.Cfg{},
},
claims: &azureClaims{
PreferredUsername: "",
Roles: []string{},
Groups: []string{"group1", "group3"},
Name: "My Name",
ID: "1234",
Email: "me@example.com",
},
want: &social.BasicUserInfo{
Id: "1234",
Name: "My Name",
Email: "me@example.com",
Login: "me@example.com",
OrgRoles: map[int64]org.RoleType{4: org.RoleEditor, 5: org.RoleViewer},
Groups: []string{"group1", "group3"},
},
},
{
name: "should return error when roles claim is empty and org mapping doesn't evaluate to any role and role attribute strict is enabled",
fields: fields{
providerCfg: &social.OAuthInfo{
Name: "azuread",
ClientId: "client-id-example",
RoleAttributeStrict: true,
OrgMapping: []string{"group1:Org4:Editor"},
},
cfg: &setting.Cfg{},
},
claims: &azureClaims{
PreferredUsername: "",
Roles: []string{},
Groups: []string{"group2"},
Name: "My Name",
ID: "1234",
},
wantErr: true,
},
{
name: "should return error when roles claim is empty and org mapping is empty and role attribute strict is enabled",
fields: fields{
providerCfg: &social.OAuthInfo{
Name: "azuread",
ClientId: "client-id-example",
RoleAttributeStrict: true,
OrgMapping: []string{},
},
cfg: &setting.Cfg{},
},
claims: &azureClaims{
PreferredUsername: "",
Roles: []string{},
Groups: []string{"group2"},
Name: "My Name",
ID: "1234",
},
wantErr: true,
},
} }
privateKey, err := rsa.GenerateKey(rand.Reader, 2048) privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
@ -711,7 +837,13 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := NewAzureADProvider(tt.fields.providerCfg, tt.fields.cfg, nil, &ssosettingstests.MockService{}, featuremgmt.WithFeatures(), cache) s := NewAzureADProvider(tt.fields.providerCfg,
tt.fields.cfg,
ProvideOrgRoleMapper(tt.fields.cfg,
&orgtest.FakeOrgService{ExpectedOrgs: []*org.OrgDTO{{ID: 4, Name: "Org4"}, {ID: 5, Name: "Org5"}}}),
&ssosettingstests.MockService{},
featuremgmt.WithFeatures(),
cache)
if tt.fields.usGovURL { if tt.fields.usGovURL {
s.SocialBase.Endpoint.AuthURL = usGovAuthURL s.SocialBase.Endpoint.AuthURL = usGovAuthURL
@ -765,11 +897,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
tt.args.client = s.Client(context.Background(), token) tt.args.client = s.Client(context.Background(), token)
got, err := s.UserInfo(context.Background(), tt.args.client, token) got, err := s.UserInfo(context.Background(), tt.args.client, token)
if (err != nil) != tt.wantErr { if tt.wantErr {
t.Errorf("UserInfo() error = %v, wantErr %v", err, tt.wantErr) require.Error(t, err)
return return
} }
require.NoError(t, err)
require.EqualValues(t, tt.want, got) require.EqualValues(t, tt.want, got)
}) })
} }
@ -791,7 +924,7 @@ func TestSocialAzureAD_SkipOrgRole(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{ {
name: "Grafana Admin and Editor roles in claim, skipOrgRoleSync disabled should get roles, skipOrgRoleSyncBase disabled", name: "Grafana Admin and Editor roles in claim, skipOrgRoleSync disabled should get roles",
fields: fields{ fields: fields{
providerCfg: &social.OAuthInfo{ providerCfg: &social.OAuthInfo{
Name: "azuread", Name: "azuread",
@ -815,19 +948,19 @@ func TestSocialAzureAD_SkipOrgRole(t *testing.T) {
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Admin", OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
IsGrafanaAdmin: trueBoolPtr(), IsGrafanaAdmin: trueBoolPtr(),
Groups: []string{}, Groups: []string{},
}, },
}, },
{ {
name: "Grafana Admin and Editor roles in claim, skipOrgRoleSync disabled should not get roles", name: "Grafana Admin and Editor roles in claim, skipOrgRoleSync enabled should not get roles",
fields: fields{ fields: fields{
providerCfg: &social.OAuthInfo{ providerCfg: &social.OAuthInfo{
Name: "azuread", Name: "azuread",
ClientId: "client-id-example", ClientId: "client-id-example",
AllowAssignGrafanaAdmin: true, AllowAssignGrafanaAdmin: true,
SkipOrgRoleSync: false, SkipOrgRoleSync: true,
}, },
cfg: &setting.Cfg{ cfg: &setting.Cfg{
AutoAssignOrgRole: "", AutoAssignOrgRole: "",
@ -841,13 +974,11 @@ func TestSocialAzureAD_SkipOrgRole(t *testing.T) {
ID: "1234", ID: "1234",
}, },
want: &social.BasicUserInfo{ want: &social.BasicUserInfo{
Id: "1234", Id: "1234",
Name: "My Name", Name: "My Name",
Email: "me@example.com", Email: "me@example.com",
Login: "me@example.com", Login: "me@example.com",
Role: "Admin", Groups: []string{},
IsGrafanaAdmin: trueBoolPtr(),
Groups: []string{},
}, },
}, },
} }
@ -884,7 +1015,13 @@ func TestSocialAzureAD_SkipOrgRole(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := NewAzureADProvider(tt.fields.providerCfg, tt.fields.cfg, nil, &ssosettingstests.MockService{}, featuremgmt.WithFeatures(), cache) s := NewAzureADProvider(tt.fields.providerCfg,
tt.fields.cfg,
ProvideOrgRoleMapper(tt.fields.cfg,
&orgtest.FakeOrgService{ExpectedOrgs: []*org.OrgDTO{{ID: 4, Name: "Org4"}, {ID: 5, Name: "Org5"}}}),
&ssosettingstests.MockService{},
featuremgmt.WithFeatures(),
cache)
s.SocialBase.Endpoint.AuthURL = authURL s.SocialBase.Endpoint.AuthURL = authURL

View File

@ -177,18 +177,6 @@ func (s *SocialBase) extractOrgs(rawJSON []byte) ([]string, error) {
return util.SearchJSONForStringSliceAttr(s.info.OrgAttributePath, rawJSON) return util.SearchJSONForStringSliceAttr(s.info.OrgAttributePath, rawJSON)
} }
// defaultRole returns the default role for the user based on the autoAssignOrgRole setting
// if legacy is enabled "" is returned indicating the previous role assignment is used.
func (s *SocialBase) defaultRole() org.RoleType {
if s.cfg.AutoAssignOrgRole != "" {
s.log.Debug("No role found, returning default.")
return org.RoleType(s.cfg.AutoAssignOrgRole)
}
// should never happen
return org.RoleViewer
}
func (s *SocialBase) isGroupMember(groups []string) bool { func (s *SocialBase) isGroupMember(groups []string) bool {
if len(s.info.AllowedGroups) == 0 { if len(s.info.AllowedGroups) == 0 {
return true return true

View File

@ -168,8 +168,8 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
// This is required to implement OrgRole mapping for OAuth providers step by step // This is required to implement OrgRole mapping for OAuth providers step by step
switch c.providerName { switch c.providerName {
case social.GenericOAuthProviderName, social.GitHubProviderName, case social.GenericOAuthProviderName, social.GitHubProviderName, social.GitlabProviderName,
social.GitlabProviderName, social.OktaProviderName, social.GoogleProviderName: social.OktaProviderName, social.GoogleProviderName, social.AzureADProviderName:
// Do nothing, these providers already supports OrgRole mapping // Do nothing, these providers already supports OrgRole mapping
default: default:
userInfo.OrgRoles, userInfo.IsGrafanaAdmin, _ = getRoles(c.cfg, func() (org.RoleType, *bool, error) { userInfo.OrgRoles, userInfo.IsGrafanaAdmin, _ = getRoles(c.cfg, func() (org.RoleType, *bool, error) {