Merge branch '2021.9.x-feature-idp-roles' into 2021.9.x

This commit is contained in:
Denis Arh
2021-12-01 08:36:50 +01:00
8 changed files with 198 additions and 37 deletions
+11 -3
View File
@@ -2,9 +2,12 @@ package oauth2
import (
"fmt"
"github.com/cortezaproject/corteza-server/auth/request"
"github.com/go-oauth2/oauth2/v4/server"
"net/http"
"github.com/cortezaproject/corteza-server/auth/request"
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/go-oauth2/oauth2/v4/server"
)
func NewUserAuthorizer(sm *request.SessionManager, loginURL, clientAuthURL string) server.UserAuthorizationHandler {
@@ -40,7 +43,12 @@ func NewUserAuthorizer(sm *request.SessionManager, loginURL, clientAuthURL strin
var roles = request.GetRoleMemberships(ses)
if client.Security != nil {
// filter user's roles with client security settings
roles = client.Security.ProcessRoles(roles...)
roles = internalAuth.ApplyRoleSecurity(
payload.ParseUint64s(client.Security.PermittedRoles),
payload.ParseUint64s(client.Security.ProhibitedRoles),
payload.ParseUint64s(client.Security.ForcedRoles),
roles...,
)
}
// User authenticated, client authorized!
+41
View File
@@ -0,0 +1,41 @@
package auth
import (
"sort"
"github.com/cortezaproject/corteza-server/pkg/slice"
)
// ApplyRoleSecurity takes role security params (set of permitted, prohibited and forced roles)
// and applies these rules to the set of given roles
//
// Filtered set of roles is returned
//
// String slices are used intentionally, because of the data source used
func ApplyRoleSecurity(permitted, prohibited, forced []uint64, rr ...uint64) (out []uint64) {
var (
mPermitted = slice.ToUint64BoolMap(permitted)
mProhibited = slice.ToUint64BoolMap(prohibited)
mForced = slice.ToUint64BoolMap(forced)
)
// iterate over user's roles and just append them (obeying allow&deny rules)
// to list of mForced roles
for _, r := range rr {
if (len(mPermitted) == 0 || mPermitted[r]) && !mProhibited[r] {
mForced[r] = true
}
}
out = make([]uint64, 0, len(mForced))
for forcedRoleID := range mForced {
out = append(out, forcedRoleID)
}
// for stable output
sort.Slice(out, func(i, j int) bool {
return out[i] < out[j]
})
return
}
+56
View File
@@ -0,0 +1,56 @@
package auth
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestApplyRoleSecurity(t *testing.T) {
tests := []struct {
name string
permitted []uint64
prohibited []uint64
forced []uint64
roles []uint64
wantOut []uint64
}{
{
"empty",
[]uint64{},
[]uint64{},
[]uint64{},
[]uint64{},
[]uint64{},
},
{
"nil",
nil,
nil,
nil,
nil,
[]uint64{},
},
{
"one",
[]uint64{1},
[]uint64{2},
[]uint64{3},
[]uint64{1, 2},
[]uint64{1, 3},
},
{
"forced only",
[]uint64{},
[]uint64{},
[]uint64{3, 2, 1},
[]uint64{2},
[]uint64{1, 2, 3},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.wantOut, ApplyRoleSecurity(tt.permitted, tt.prohibited, tt.forced, tt.roles...))
})
}
}
+34
View File
@@ -15,6 +15,7 @@ import (
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/cortezaproject/corteza-server/pkg/rand"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/service/event"
@@ -962,6 +963,39 @@ func (svc auth) procLogin(ctx context.Context, s store.Storer, u *types.User, c
return
}
{
var eapSec *types.ExternalAuthProviderSecurity
switch p.Provider {
case credentialsTypePassword:
// nothing to do with password provider
case "saml":
// we need to fetch SAML security settings from different part of settings
eapSec = &CurrentSettings.Auth.External.Saml.Security
default:
eap := CurrentSettings.Auth.External.Providers.FindByHandle(p.Provider)
if eap != nil {
eapSec = &eap.Security
}
}
if eapSec != nil {
// if authenticated with external auth provider
// there might be additional roles that need to be
// set to this security session
u.SetRoles(internalAuth.ApplyRoleSecurity(
payload.ParseUint64s(eapSec.PermittedRoles),
payload.ParseUint64s(eapSec.ProhibitedRoles),
payload.ParseUint64s(eapSec.ForcedRoles),
u.Roles()...,
)...)
}
}
if c != nil {
c.LastUsedAt = now()
if err = store.UpdateCredentials(ctx, s, c); err != nil {
+4
View File
@@ -232,6 +232,10 @@ func (svc *settings) BulkSet(ctx context.Context, vv types.SettingValueSet) (err
return
}
if err = store.DeleteSetting(ctx, svc.store, vv.Trash()...); err != nil {
return
}
svc.logChange(ctx, vv)
return svc.updateCurrent(ctx, vv)
+34
View File
@@ -110,6 +110,8 @@ type (
IdentHandle string `kv:"ident-handle"`
IdentIdentifier string `kv:"ident-identifier"`
} `kv:"idp"`
Security ExternalAuthProviderSecurity `json:"-" kv:"security,final"`
}
// all external providers we know
@@ -223,6 +225,38 @@ type (
RedirectUrl string `json:"-" kv:"redirect"`
IssuerUrl string `json:"-" kv:"issuer"`
Weight int `json:"-"`
Security ExternalAuthProviderSecurity `json:"-" kv:"security,final"`
}
ExternalAuthProviderSecurity struct {
// Subset of roles, permitted to be used with this client
// when authorizing via this auth provider.
//
// IDs are intentionally stored as strings to support JS (int64 only)
//
PermittedRoles []string `json:"permittedRoles,omitempty"`
// Subset of roles, prohibited to be used with this client
// when authorizing via this auth provider.
//
// IDs are intentionally stored as strings to support JS (int64 only)
//
ProhibitedRoles []string `json:"prohibitedRoles,omitempty"`
// Set of additional roles that are forced on this user
// when authorizing via this auth provider.
//
// IDs are intentionally stored as strings to support JS (int64 only)
ForcedRoles []string `json:"forcedRoles,omitempty"`
// Map external roles or groups to internal
//
// If IdP provides a list of roles (groups) along side authenticated user
// these roles can be mapped to the valid local roles
//
// @todo implement mapped roles
// MappedRoles map[string]string `json:"mappedRoles,omitempty"`
}
SmtpServers struct {
-31
View File
@@ -4,11 +4,9 @@ import (
"database/sql/driver"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/slice"
)
type (
@@ -221,32 +219,3 @@ func (vv *AuthClientSecurity) Value() (driver.Value, error) {
return json.Marshal(vv)
}
// Takes user's roles, filter out only allowed roles (when set), remove denied and add all forced
func (vv *AuthClientSecurity) ProcessRoles(rr ...uint64) (out []uint64) {
var (
permitted = slice.ToStringBoolMap(vv.PermittedRoles)
prohibited = slice.ToStringBoolMap(vv.ProhibitedRoles)
forced = slice.ToStringBoolMap(vv.ForcedRoles)
aux string
roleID uint64
)
// iterate over user's roles and just append them (obeying allow&deny rules)
// to list of forced roles
for _, r := range rr {
aux = strconv.FormatUint(r, 10)
if (len(vv.PermittedRoles) == 0 || permitted[aux]) && !prohibited[aux] {
forced[aux] = true
}
}
out = make([]uint64, 0, len(forced))
for i := range forced {
if roleID, _ = strconv.ParseUint(i, 10, 64); roleID > 0 {
out = append(out, roleID)
}
}
return
}
+18 -3
View File
@@ -88,6 +88,10 @@ func (v *SettingValue) Bool() (out bool) {
return
}
func (v *SettingValue) IsNull() (out bool) {
return v == nil || string(v.Value) == "null" || string(v.Value) == ""
}
func (v *SettingValue) NormalizeValue() {
}
@@ -232,7 +236,7 @@ func (set SettingValueSet) New(in SettingValueSet) (out SettingValueSet) {
org := set.KV()
for _, v := range in {
if !org.Has(v.Name) {
if !org.Has(v.Name) && !v.IsNull() {
out = append(out, v)
}
}
@@ -240,12 +244,23 @@ func (set SettingValueSet) New(in SettingValueSet) (out SettingValueSet) {
return
}
// New returns all new values (that do not exist in the original set)
// New returns all old values (that exist in the original set)
func (set SettingValueSet) Old(in SettingValueSet) (out SettingValueSet) {
org := set.KV()
for _, v := range in {
if org.Has(v.Name) {
if org.Has(v.Name) && !v.IsNull() {
out = append(out, v)
}
}
return
}
// Trash returns values from the set that were set ti nil
func (set SettingValueSet) Trash() (out SettingValueSet) {
for _, v := range set {
if v.IsNull() {
out = append(out, v)
}
}