Skip to content

Commit

Permalink
AUTH-6145 support multi-valued + service token auth for scim provisio…
Browse files Browse the repository at this point in the history
…ning
  • Loading branch information
khiller-cf committed Dec 6, 2024
1 parent b95ee61 commit fc9774d
Show file tree
Hide file tree
Showing 2 changed files with 302 additions and 1 deletion.
67 changes: 66 additions & 1 deletion access_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,42 @@ const (
AccessApplicationScimAuthenticationSchemeHttpBasic AccessApplicationScimAuthenticationScheme = "httpbasic"
AccessApplicationScimAuthenticationSchemeOauthBearerToken AccessApplicationScimAuthenticationScheme = "oauthbearertoken"
AccessApplicationScimAuthenticationSchemeOauth2 AccessApplicationScimAuthenticationScheme = "oauth2"
ScimAuthenticationAccessServiceToken AccessApplicationScimAuthenticationScheme = "access_service_token"
)

type ScimAuthentication interface {
isScimAuthentication()
isMultipleScimAuthentication() bool
}

type SingleScimAuthentication interface {
isSingleScimAuthentication()
isScimAuthentication()
isMultipleScimAuthentication() bool
}

type AccessApplicationMultipleScimAuthentication []*AccessApplicationScimAuthenticationSingleJSON

func (m *AccessApplicationMultipleScimAuthentication) isScimAuthentication() {}

func (m *AccessApplicationMultipleScimAuthentication) isMultipleScimAuthentication() bool {
return true
}

type AccessApplicationScimAuthenticationSingleJSON struct {
Value SingleScimAuthentication
}

type AccessApplicationScimAuthenticationJson struct {
Value AccessApplicationScimAuthentication
}

func (a *AccessApplicationScimAuthenticationJson) UnmarshalJSON(buf []byte) error {
if len(buf) > 0 && rune(buf[0]) == '[' {
a.Value = new(AccessApplicationMultipleScimAuthentication)
return json.Unmarshal(buf, a.Value)
}

var scheme baseScimAuthentication
if err := json.Unmarshal(buf, &scheme); err != nil {
return err
Expand All @@ -129,6 +158,8 @@ func (a *AccessApplicationScimAuthenticationJson) UnmarshalJSON(buf []byte) erro
a.Value = new(AccessApplicationScimAuthenticationOauthBearerToken)
case AccessApplicationScimAuthenticationSchemeOauth2:
a.Value = new(AccessApplicationScimAuthenticationOauth2)
case ScimAuthenticationAccessServiceToken:
a.Value = new(AccessApplicationScimAuthenticationServiceToken)
default:
return errors.New("invalid authentication scheme")
}
Expand All @@ -140,15 +171,43 @@ func (a *AccessApplicationScimAuthenticationJson) MarshalJSON() ([]byte, error)
return json.Marshal(a.Value)
}

func (a *AccessApplicationScimAuthenticationSingleJSON) UnmarshalJSON(buf []byte) error {
var scheme baseScimAuthentication
if err := json.Unmarshal(buf, &scheme); err != nil {
return err
}
switch scheme.Scheme {
case AccessApplicationScimAuthenticationSchemeHttpBasic:
a.Value = new(AccessApplicationScimAuthenticationHttpBasic)
case AccessApplicationScimAuthenticationSchemeOauthBearerToken:
a.Value = new(AccessApplicationScimAuthenticationOauthBearerToken)
case AccessApplicationScimAuthenticationSchemeOauth2:
a.Value = new(AccessApplicationScimAuthenticationOauth2)
case ScimAuthenticationAccessServiceToken:
a.Value = new(AccessApplicationScimAuthenticationServiceToken)
default:
return errors.New("invalid authentication scheme")
}

return json.Unmarshal(buf, a.Value)
}

func (a AccessApplicationScimAuthenticationSingleJSON) MarshalJSON() ([]byte, error) {
return json.Marshal(a.Value)
}

type AccessApplicationScimAuthentication interface {
isScimAuthentication()
isMultipleScimAuthentication() bool
}

type baseScimAuthentication struct {
Scheme AccessApplicationScimAuthenticationScheme `json:"scheme"`
}

func (baseScimAuthentication) isScimAuthentication() {}
func (baseScimAuthentication) isScimAuthentication() {}
func (baseScimAuthentication) isSingleScimAuthentication() {}
func (baseScimAuthentication) isMultipleScimAuthentication() bool { return false }

type AccessApplicationScimAuthenticationHttpBasic struct {
baseScimAuthentication
Expand All @@ -170,6 +229,12 @@ type AccessApplicationScimAuthenticationOauth2 struct {
Scopes []string `json:"scopes,omitempty"`
}

type AccessApplicationScimAuthenticationServiceToken struct {
baseScimAuthentication
ClientID string `json:"client_id,omitempty" validate:"required_with=ClientSecret,excluded_with=ServiceTokenID"`
ClientSecret string `json:"client_secret,omitempty" validate:"required_with=ClientID,excluded_with=ServiceTokenID"`
}

type AccessApplicationScimMapping struct {
Schema string `json:"schema"`
Enabled *bool `json:"enabled,omitempty"`
Expand Down
236 changes: 236 additions & 0 deletions access_application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1604,3 +1604,239 @@ func TestCreateAccessApplicationWithSCIMProvisioning(t *testing.T) {
assert.Equal(t, fullAccessApplication, actual)
}
}

func TestCreateAccessApplicationWithSCIMProvisioningMultiAuthentication(t *testing.T) {
setup()
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "480f4f69-1a28-4fdd-9240-1ed29f0ac1db",
"created_at": "2014-01-01T05:20:00.12345Z",
"updated_at": "2014-01-01T05:20:00.12345Z",
"aud": "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893",
"name": "Admin SCIM App",
"domain": "example.cloudflareaccess.com/cdn-cgi/access/sso/oidc/737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893",
"type": "saas",
"session_duration": "24h",
"allowed_idps": [],
"auto_redirect_to_identity": false,
"enable_binding_cookie": false,
"custom_deny_url": "https://www.example.com",
"custom_deny_message": "denied!",
"logo_url": "https://www.example.com/example.png",
"skip_interstitial": true,
"app_launcher_visible": true,
"service_auth_401_redirect": true,
"custom_non_identity_deny_url": "https://blocked.com",
"tags": ["engineers"],
"scim_config": {
"enabled": true,
"remote_uri": "https://scim.com",
"authentication": [{
"scheme": "oauthbearertoken",
"token": "1234567890"
}, {
"scheme": "access_service_token",
"client_id": "1234",
"client_secret": "5678"
}],
"idp_uid": "1234567",
"deactivate_on_delete": true,
"mappings": [
{
"schema": "urn:ietf:params:scim:schemas:core:2.0:User",
"enabled": true,
"filter": "title pr or userType eq \"Intern\"",
"transform_jsonata": "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])",
"operations": {
"create": true,
"update": true,
"delete": true
},
"strictness": "passthrough"
}
]
}
}
}
`)
}

createdAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
updatedAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
multiAuth := new(AccessApplicationMultipleScimAuthentication)

*multiAuth = append(*multiAuth, &AccessApplicationScimAuthenticationSingleJSON{
Value: &AccessApplicationScimAuthenticationOauthBearerToken{
Token: "1234567890",
baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken},
},
})

*multiAuth = append(*multiAuth, &AccessApplicationScimAuthenticationSingleJSON{
Value: &AccessApplicationScimAuthenticationServiceToken{
baseScimAuthentication: baseScimAuthentication{Scheme: ScimAuthenticationAccessServiceToken},
ClientID: "1234",
ClientSecret: "1234",
},
})
fullAccessApplication := AccessApplication{
ID: "480f4f69-1a28-4fdd-9240-1ed29f0ac1db",
Name: "Admin SCIM App",
Domain: "example.cloudflareaccess.com/cdn-cgi/access/sso/oidc/737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893",
Type: "saas",
SessionDuration: "24h",
AUD: "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893",
AllowedIdps: []string{},
AutoRedirectToIdentity: BoolPtr(false),
EnableBindingCookie: BoolPtr(false),
AppLauncherVisible: BoolPtr(true),
ServiceAuth401Redirect: BoolPtr(true),
CustomDenyMessage: "denied!",
CustomDenyURL: "https://www.example.com",
LogoURL: "https://www.example.com/example.png",
SkipInterstitial: BoolPtr(true),
CreatedAt: &createdAt,
UpdatedAt: &updatedAt,
CustomNonIdentityDenyURL: "https://blocked.com",
Tags: []string{"engineers"},
SCIMConfig: &AccessApplicationSCIMConfig{
Enabled: BoolPtr(true),
RemoteURI: "https://scim.com",
Authentication: &AccessApplicationScimAuthenticationJson{
Value: &AccessApplicationMultipleScimAuthentication{
&AccessApplicationScimAuthenticationSingleJSON{
Value: &AccessApplicationScimAuthenticationOauthBearerToken{
Token: "1234567890",
baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken},
},
},
&AccessApplicationScimAuthenticationSingleJSON{
Value: &AccessApplicationScimAuthenticationServiceToken{
baseScimAuthentication: baseScimAuthentication{Scheme: ScimAuthenticationAccessServiceToken},
ClientID: "1234",
ClientSecret: "5678",
},
},
},
},
IdPUID: "1234567",
DeactivateOnDelete: BoolPtr(true),
Mappings: []*AccessApplicationScimMapping{
{
Schema: "urn:ietf:params:scim:schemas:core:2.0:User",
Enabled: BoolPtr(true),
Filter: "title pr or userType eq \"Intern\"",
TransformJsonata: "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])",
Operations: &AccessApplicationScimMappingOperations{
Create: BoolPtr(true),
Update: BoolPtr(true),
Delete: BoolPtr(true),
},
Strictness: "passthrough",
},
},
},
}

mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", handler)

actual, err := client.CreateAccessApplication(context.Background(), AccountIdentifier(testAccountID), CreateAccessApplicationParams{
Name: "Admin Saas Site",
SCIMConfig: &AccessApplicationSCIMConfig{
Enabled: BoolPtr(true),
RemoteURI: "https://scim.com",
Authentication: &AccessApplicationScimAuthenticationJson{
Value: &AccessApplicationMultipleScimAuthentication{
&AccessApplicationScimAuthenticationSingleJSON{
Value: &AccessApplicationScimAuthenticationOauthBearerToken{
Token: "1234567890",
baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken},
},
},
&AccessApplicationScimAuthenticationSingleJSON{
Value: &AccessApplicationScimAuthenticationServiceToken{
ClientID: "1234",
ClientSecret: "5678",
baseScimAuthentication: baseScimAuthentication{Scheme: ScimAuthenticationAccessServiceToken},
},
},
},
},
IdPUID: "1234567",
DeactivateOnDelete: BoolPtr(true),
Mappings: []*AccessApplicationScimMapping{
{
Schema: "urn:ietf:params:scim:schemas:core:2.0:User",
Enabled: BoolPtr(true),
Filter: "title pr or userType eq \"Intern\"",
TransformJsonata: "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])",
Operations: &AccessApplicationScimMappingOperations{
Create: BoolPtr(true),
Update: BoolPtr(true),
Delete: BoolPtr(true),
},
Strictness: "strict",
},
},
},
})

if assert.NoError(t, err) {
assert.Equal(t, fullAccessApplication, actual)
}

mux.HandleFunc("/zones/"+testZoneID+"/access/apps", handler)

actual, err = client.CreateAccessApplication(context.Background(), ZoneIdentifier(testZoneID), CreateAccessApplicationParams{
Name: "Admin SCIM Site",
SCIMConfig: &AccessApplicationSCIMConfig{
Enabled: BoolPtr(true),
RemoteURI: "https://scim.com",
Authentication: &AccessApplicationScimAuthenticationJson{
Value: &AccessApplicationMultipleScimAuthentication{
&AccessApplicationScimAuthenticationSingleJSON{
Value: &AccessApplicationScimAuthenticationOauthBearerToken{
Token: "1234567890",
baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken},
},
},
&AccessApplicationScimAuthenticationSingleJSON{
Value: &AccessApplicationScimAuthenticationServiceToken{
ClientID: "1234",
ClientSecret: "5678",
baseScimAuthentication: baseScimAuthentication{Scheme: ScimAuthenticationAccessServiceToken},
},
},
},
},
IdPUID: "1234567",
DeactivateOnDelete: BoolPtr(true),
Mappings: []*AccessApplicationScimMapping{
{
Schema: "urn:ietf:params:scim:schemas:core:2.0:User",
Enabled: BoolPtr(true),
Filter: "title pr or userType eq \"Intern\"",
TransformJsonata: "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])",
Operations: &AccessApplicationScimMappingOperations{
Create: BoolPtr(true),
Update: BoolPtr(true),
Delete: BoolPtr(true),
},
Strictness: "strict",
},
},
},
})

if assert.NoError(t, err) {
assert.Equal(t, fullAccessApplication, actual)
}
}

0 comments on commit fc9774d

Please sign in to comment.