Skip to content

Commit

Permalink
AUTH-5816 adds scim_config to Access applications
Browse files Browse the repository at this point in the history
  • Loading branch information
khiller-cf committed May 3, 2024
1 parent a9d982f commit 82ff42f
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/1921.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
access_application: add support for `scim_config`
```
91 changes: 91 additions & 0 deletions access_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cloudflare

import (
"context"
"errors"
"fmt"
"net/http"
"time"
Expand Down Expand Up @@ -56,6 +57,7 @@ type AccessApplication struct {
OptionsPreflightBypass *bool `json:"options_preflight_bypass,omitempty"`
CustomPages []string `json:"custom_pages,omitempty"`
Tags []string `json:"tags,omitempty"`
ScimConfig *AccessApplicationScimConfig `json:"scim_config,omitempty"`
AccessAppLauncherCustomization
}

Expand All @@ -76,6 +78,92 @@ type AccessApplicationCorsHeaders struct {
MaxAge int `json:"max_age,omitempty"`
}

// AccessApplicationScimConfig represents the configuration for provisioning to an Access Application via SCIM.
type AccessApplicationScimConfig struct {
Enabled *bool `json:"enabled,omitempty"`
RemoteURI string `json:"remote_uri,omitempty"`
Authentication *AccessApplicationScimAuthenticationJson `json:"authentication,omitempty"`
IdpUid string `json:"idp_uid,omitempty"`
DeactivateOnDelete *bool `json:"deactivate_on_delete,omitempty"`
Mappings []*AccessApplicationScimMapping `json:"mappings,omitempty" validate:"unique=Schema,dive"`
}

type AccessApplicationScimAuthenticationScheme string

const (
AccessApplicationScimAuthenticationSchemeHttpBasic AccessApplicationScimAuthenticationScheme = "httpbasic"
AccessApplicationScimAuthenticationSchemeOauthBearerToken AccessApplicationScimAuthenticationScheme = "oauthbearertoken"
AccessApplicationScimAuthenticationSchemeOauth2 AccessApplicationScimAuthenticationScheme = "oauth2"
)

type AccessApplicationScimAuthenticationJson struct {
Value AccessApplicationScimAuthentication
}

func (a *AccessApplicationScimAuthenticationJson) 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)
default:
return errors.New("invalid authentication scheme")
}

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

type AccessApplicationScimAuthentication interface {
isScimAuthentication()
}

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

func (baseScimAuthentication) isScimAuthentication() {}

type AccessApplicationScimAuthenticationHttpBasic struct {
baseScimAuthentication
User string `json:"user"`
Password string `json:"password"`
}

type AccessApplicationScimAuthenticationOauthBearerToken struct {
baseScimAuthentication
Token string `json:"token"`
}

type AccessApplicationScimAuthenticationOauth2 struct {
baseScimAuthentication
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
AuthorizationURL string `json:"authorization_url"`
TokenURL string `json:"token_url"`
Scopes []string `json:"scopes,omitempty"`
}

type AccessApplicationScimMapping struct {
Schema string `json:"schema"`
Enabled *bool `json:"enabled,omitempty"`
Filter string `json:"filter,omitempty"`
TransformJsonata string `json:"transform_jsonata,omitempty"`
Operations *AccessApplicationScimMappingOperations `json:"operations,omitempty"`
}

type AccessApplicationScimMappingOperations struct {
Create *bool `json:"create,omitempty"`
Update *bool `json:"update,omitempty"`
Delete *bool `json:"delete,omitempty"`
}

// AccessApplicationListResponse represents the response from the list
// access applications endpoint.
type AccessApplicationListResponse struct {
Expand Down Expand Up @@ -155,6 +243,7 @@ type AccessLandingPageDesign struct {
ButtonColor string `json:"button_color"`
ButtonTextColor string `json:"button_text_color"`
}

type ListAccessApplicationsParams struct {
ResultInfo
}
Expand Down Expand Up @@ -187,6 +276,7 @@ type CreateAccessApplicationParams struct {
AllowAuthenticateViaWarp *bool `json:"allow_authenticate_via_warp,omitempty"`
CustomPages []string `json:"custom_pages,omitempty"`
Tags []string `json:"tags,omitempty"`
ScimConfig *AccessApplicationScimConfig `json:"scim_config,omitempty"`
AccessAppLauncherCustomization
}

Expand Down Expand Up @@ -219,6 +309,7 @@ type UpdateAccessApplicationParams struct {
OptionsPreflightBypass *bool `json:"options_preflight_bypass,omitempty"`
CustomPages []string `json:"custom_pages,omitempty"`
Tags []string `json:"tags,omitempty"`
ScimConfig *AccessApplicationScimConfig `json:"scim_config,omitempty"`
AccessAppLauncherCustomization
}

Expand Down
179 changes: 179 additions & 0 deletions access_application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1013,3 +1013,182 @@ func TestCreateApplicationWithAccessAppLauncherCustomization(t *testing.T) {
assert.Equal(t, fullAccessApplication, actual)
}
}

func TestCreateAccessApplicationWithSCIMProvisioning(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"
},
"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
}
}
]
}
}
}
`)
}

createdAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
updatedAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
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: &AccessApplicationScimAuthenticationOauthBearerToken{
Token: "1234567890",
baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken},
},
},
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),
},
},
},
},
}

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

actual, err := client.CreateAccessApplication(context.Background(), AccountIdentifier(testAccountID), CreateAccessApplicationParams{
Name: "Admin Saas Site",
ScimConfig: &AccessApplicationScimConfig{
Enabled: true,
RemoteURI: "https://scim.com",
Authentication: &AccessApplicationScimAuthenticationJson{
Value: &AccessApplicationScimAuthenticationOauthBearerToken{
Token: "1234567890",
baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken},
},
},
IdpUid: "1234567",
DeactivateOnDelete: true,
Mappings: []*AccessApplicationScimMapping{
{
Schema: "urn:ietf:params:scim:schemas:core:2.0:User",
Enabled: 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),
},
},
},
},
})

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: true,
RemoteURI: "https://scim.com",
Authentication: &AccessApplicationScimAuthenticationJson{
Value: &AccessApplicationScimAuthenticationOauthBearerToken{
Token: "1234567890",
baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken},
},
},
IdpUid: "1234567",
DeactivateOnDelete: true,
Mappings: []*AccessApplicationScimMapping{
{
Schema: "urn:ietf:params:scim:schemas:core:2.0:User",
Enabled: 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),
},
},
},
},
})

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

0 comments on commit 82ff42f

Please sign in to comment.