Repository: cloudflare/cloudflare-go
Pull request #1344 (2023-08-01 14:41:10)

AUTH-5315 adds new conditional_access_enabled attr to access IDP
## Description

* adds new conditional_access_enabled attr to access IDP
* list/put IDP auth context endpoints
* auth context rule type

<!--
Describe your changes in detail through motivation and context. Why is
this change required? What problem does it solve? If it fixes an open
issue, link to the issue using GitHub's closing issues keywords[1].
-->
## Has your change been tested?

* Added unit tests for policy creation w/new rule and auth context endpoints

## Types of changes

What sort of change does your code introduce/modify?

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

- [x] My code follows the code style of this project.
- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] This change is using publicly documented in [cloudflare/api-schemas](https://github.com/cloudflare/api-schemas) 
      and relies on stable APIs.



Patch:
diff --git a/.changelog/1343.txt b/.changelog/1343.txt
deleted file mode 100644
index df6721b23..000000000
--- a/.changelog/1343.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-```release-note:enhancement
-access_application: Add support for custom pages
-```
-
-```release-note:enhancement
-access_custom_page: Add support for custom pages
-```
-
-```release-note:enhancement
-access_organization: add support for custom pages
-```
diff --git a/.changelog/1344.txt b/.changelog/1344.txt
new file mode 100644
index 000000000..f800df64e
--- /dev/null
+++ b/.changelog/1344.txt
@@ -0,0 +1,11 @@
+```release-note:enhancement
+access_identity_provider: add attr conditional_access_enabled
+```
+
+```release-note:enhancement
+access_group: add auth_context group ruletype
+```
+
+```release-note:enhancement
+access_identity_provider: add auth context list/put endpoint
+```
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 942428505..e6f02291b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,6 @@
 
 ENHANCEMENTS:
 
-* access_application: Add support for custom pages ([#1343](https://github.com/cloudflare/cloudflare-go/issues/1343))
-* access_custom_page: Add support for custom pages ([#1343](https://github.com/cloudflare/cloudflare-go/issues/1343))
-* access_organization: add support for custom pages ([#1343](https://github.com/cloudflare/cloudflare-go/issues/1343))
 * teams-accounts: Adds support for protocol detection ([#1340](https://github.com/cloudflare/cloudflare-go/issues/1340))
 * workers: Add `pipeline_hash` field to Workers script response struct. ([#1330](https://github.com/cloudflare/cloudflare-go/issues/1330))
 * workers: Add support for declaring arbitrary bindings with UnsafeBinding. ([#1330](https://github.com/cloudflare/cloudflare-go/issues/1330))
diff --git a/access_application.go b/access_application.go
index c43faf0c8..1c8bd66e6 100644
--- a/access_application.go
+++ b/access_application.go
@@ -50,7 +50,6 @@ type AccessApplication struct {
 	HttpOnlyCookieAttribute *bool                          `json:"http_only_cookie_attribute,omitempty"`
 	ServiceAuth401Redirect  *bool                          `json:"service_auth_401_redirect,omitempty"`
 	PathCookieAttribute     *bool                          `json:"path_cookie_attribute,omitempty"`
-	CustomPages             []string                       `json:"custom_pages,omitempty"`
 }
 
 type AccessApplicationGatewayRule struct {
@@ -140,7 +139,6 @@ type CreateAccessApplicationParams struct {
 	SessionDuration         string                         `json:"session_duration,omitempty"`
 	SkipInterstitial        *bool                          `json:"skip_interstitial,omitempty"`
 	Type                    AccessApplicationType          `json:"type,omitempty"`
-	CustomPages             []string                       `json:"custom_pages,omitempty"`
 }
 
 type UpdateAccessApplicationParams struct {
@@ -167,7 +165,6 @@ type UpdateAccessApplicationParams struct {
 	SessionDuration         string                         `json:"session_duration,omitempty"`
 	SkipInterstitial        *bool                          `json:"skip_interstitial,omitempty"`
 	Type                    AccessApplicationType          `json:"type,omitempty"`
-	CustomPages             []string                       `json:"custom_pages,omitempty"`
 }
 
 // ListAccessApplications returns all applications within an account or zone.
diff --git a/access_custom_page.go b/access_custom_page.go
deleted file mode 100644
index e521fbcde..000000000
--- a/access_custom_page.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"net/http"
-)
-
-var ErrMissingUID = errors.New("required UID missing")
-
-type AccessCustomPageType string
-
-const (
-	Forbidden      AccessCustomPageType = "forbidden"
-	IdentityDenied AccessCustomPageType = "identity_denied"
-)
-
-type AccessCustomPage struct {
-	// The HTML content of the custom page.
-	CustomHTML string               `json:"custom_html,omitempty"`
-	Name       string               `json:"name,omitempty"`
-	AppCount   int                  `json:"app_count,omitempty"`
-	Type       AccessCustomPageType `json:"type,omitempty"`
-	UID        string               `json:"uid,omitempty"`
-}
-
-type AccessCustomPageListResponse struct {
-	Response
-	Result     []AccessCustomPage `json:"result"`
-	ResultInfo `json:"result_info"`
-}
-
-type AccessCustomPageResponse struct {
-	Response
-	Result AccessCustomPage `json:"result"`
-}
-
-type ListAccessCustomPagesParams struct{}
-
-type CreateAccessCustomPageParams struct {
-	CustomHTML string               `json:"custom_html,omitempty"`
-	Name       string               `json:"name,omitempty"`
-	Type       AccessCustomPageType `json:"type,omitempty"`
-}
-
-type UpdateAccessCustomPageParams struct {
-	CustomHTML string               `json:"custom_html,omitempty"`
-	Name       string               `json:"name,omitempty"`
-	Type       AccessCustomPageType `json:"type,omitempty"`
-	UID        string               `json:"uid,omitempty"`
-}
-
-func (api *API) ListAccessCustomPages(ctx context.Context, rc *ResourceContainer, params ListAccessCustomPagesParams) ([]AccessCustomPage, error) {
-	uri := buildURI(fmt.Sprintf("/%s/%s/access/custom_pages", rc.Level, rc.Identifier), params)
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return []AccessCustomPage{}, err
-	}
-
-	var customPagesResponse AccessCustomPageListResponse
-	err = json.Unmarshal(res, &customPagesResponse)
-	if err != nil {
-		return []AccessCustomPage{}, err
-	}
-	return customPagesResponse.Result, nil
-}
-
-func (api *API) GetAccessCustomPage(ctx context.Context, rc *ResourceContainer, id string) (AccessCustomPage, error) {
-	uri := fmt.Sprintf("/%s/%s/access/custom_pages/%s", rc.Level, rc.Identifier, id)
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return AccessCustomPage{}, err
-	}
-
-	var customPageResponse AccessCustomPageResponse
-	err = json.Unmarshal(res, &customPageResponse)
-	if err != nil {
-		return AccessCustomPage{}, err
-	}
-	return customPageResponse.Result, nil
-}
-
-func (api *API) CreateAccessCustomPage(ctx context.Context, rc *ResourceContainer, params CreateAccessCustomPageParams) (AccessCustomPage, error) {
-	uri := fmt.Sprintf("/%s/%s/access/custom_pages", rc.Level, rc.Identifier)
-	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
-	if err != nil {
-		return AccessCustomPage{}, err
-	}
-
-	var customPageResponse AccessCustomPageResponse
-	err = json.Unmarshal(res, &customPageResponse)
-	if err != nil {
-		return AccessCustomPage{}, err
-	}
-	return customPageResponse.Result, nil
-}
-
-func (api *API) DeleteAccessCustomPage(ctx context.Context, rc *ResourceContainer, id string) error {
-	uri := fmt.Sprintf("/%s/%s/access/custom_pages/%s", rc.Level, rc.Identifier, id)
-	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-func (api *API) UpdateAccessCustomPage(ctx context.Context, rc *ResourceContainer, params UpdateAccessCustomPageParams) (AccessCustomPage, error) {
-	if params.UID == "" {
-		return AccessCustomPage{}, ErrMissingUID
-	}
-
-	uri := fmt.Sprintf("/%s/%s/access/custom_pages/%s", rc.Level, rc.Identifier, params.UID)
-	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
-	if err != nil {
-		return AccessCustomPage{}, err
-	}
-
-	var customPageResponse AccessCustomPageResponse
-	err = json.Unmarshal(res, &customPageResponse)
-	if err != nil {
-		return AccessCustomPage{}, err
-	}
-	return customPageResponse.Result, nil
-}
diff --git a/access_group.go b/access_group.go
index c21a80eeb..90ee26193 100644
--- a/access_group.go
+++ b/access_group.go
@@ -153,6 +153,15 @@ type AccessGroupSAML struct {
 	} `json:"saml"`
 }
 
+// AccessGroupAzureAuthContext is used to configure access based on Azure auth contexts.
+type AccessGroupAzureAuthContext struct {
+	AuthContext struct {
+		ID                 string `json:"id"`
+		IdentityProviderID string `json:"identity_provider_id"`
+		ACID               string `json:"ac_id"`
+	} `json:"auth_context"`
+}
+
 // AccessGroupAuthMethod is used for managing access by the "amr"
 // (Authentication Methods References) identifier. For example, an
 // application may want to require that users authenticate using a hardware
diff --git a/access_identity_provider.go b/access_identity_provider.go
index c248bc896..743ae137c 100644
--- a/access_identity_provider.go
+++ b/access_identity_provider.go
@@ -22,29 +22,30 @@ type AccessIdentityProvider struct {
 //
 // API reference: https://developers.cloudflare.com/access/configuring-identity-providers/
 type AccessIdentityProviderConfiguration struct {
-	APIToken           string   `json:"api_token,omitempty"`
-	AppsDomain         string   `json:"apps_domain,omitempty"`
-	Attributes         []string `json:"attributes,omitempty"`
-	AuthURL            string   `json:"auth_url,omitempty"`
-	CentrifyAccount    string   `json:"centrify_account,omitempty"`
-	CentrifyAppID      string   `json:"centrify_app_id,omitempty"`
-	CertsURL           string   `json:"certs_url,omitempty"`
-	ClientID           string   `json:"client_id,omitempty"`
-	ClientSecret       string   `json:"client_secret,omitempty"`
-	Claims             []string `json:"claims,omitempty"`
-	Scopes             []string `json:"scopes,omitempty"`
-	DirectoryID        string   `json:"directory_id,omitempty"`
-	EmailAttributeName string   `json:"email_attribute_name,omitempty"`
-	IdpPublicCert      string   `json:"idp_public_cert,omitempty"`
-	IssuerURL          string   `json:"issuer_url,omitempty"`
-	OktaAccount        string   `json:"okta_account,omitempty"`
-	OneloginAccount    string   `json:"onelogin_account,omitempty"`
-	RedirectURL        string   `json:"redirect_url,omitempty"`
-	SignRequest        bool     `json:"sign_request,omitempty"`
-	SsoTargetURL       string   `json:"sso_target_url,omitempty"`
-	SupportGroups      bool     `json:"support_groups,omitempty"`
-	TokenURL           string   `json:"token_url,omitempty"`
-	PKCEEnabled        *bool    `json:"pkce_enabled,omitempty"`
+	APIToken                 string   `json:"api_token,omitempty"`
+	AppsDomain               string   `json:"apps_domain,omitempty"`
+	Attributes               []string `json:"attributes,omitempty"`
+	AuthURL                  string   `json:"auth_url,omitempty"`
+	CentrifyAccount          string   `json:"centrify_account,omitempty"`
+	CentrifyAppID            string   `json:"centrify_app_id,omitempty"`
+	CertsURL                 string   `json:"certs_url,omitempty"`
+	ClientID                 string   `json:"client_id,omitempty"`
+	ClientSecret             string   `json:"client_secret,omitempty"`
+	Claims                   []string `json:"claims,omitempty"`
+	Scopes                   []string `json:"scopes,omitempty"`
+	DirectoryID              string   `json:"directory_id,omitempty"`
+	EmailAttributeName       string   `json:"email_attribute_name,omitempty"`
+	IdpPublicCert            string   `json:"idp_public_cert,omitempty"`
+	IssuerURL                string   `json:"issuer_url,omitempty"`
+	OktaAccount              string   `json:"okta_account,omitempty"`
+	OneloginAccount          string   `json:"onelogin_account,omitempty"`
+	RedirectURL              string   `json:"redirect_url,omitempty"`
+	SignRequest              bool     `json:"sign_request,omitempty"`
+	SsoTargetURL             string   `json:"sso_target_url,omitempty"`
+	SupportGroups            bool     `json:"support_groups,omitempty"`
+	TokenURL                 string   `json:"token_url,omitempty"`
+	PKCEEnabled              *bool    `json:"pkce_enabled,omitempty"`
+	ConditionalAccessEnabled bool     `json:"conditional_access_enabled,omitempty"`
 }
 
 type AccessIdentityProviderScimConfiguration struct {
@@ -89,6 +90,22 @@ type UpdateAccessIdentityProviderParams struct {
 	ScimConfig AccessIdentityProviderScimConfiguration `json:"scim_config"`
 }
 
+// AccessAuthContext represents an Access Azure Identity Provider Auth Context.
+type AccessAuthContext struct {
+	ID          string `json:"id"`
+	UID         string `json:"uid"`
+	ACID        string `json:"ac_id"`
+	DisplayName string `json:"display_name"`
+	Description string `json:"description"`
+}
+
+// AccessAuthContextsListResponse represents the response from the list
+// Access Auth Contexts endpoint.
+type AccessAuthContextsListResponse struct {
+	Result []AccessAuthContext `json:"result"`
+	Response
+}
+
 // ListAccessIdentityProviders returns all Access Identity Providers for an
 // account or zone.
 //
@@ -235,3 +252,51 @@ func (api *API) DeleteAccessIdentityProvider(ctx context.Context, rc *ResourceCo
 
 	return accessIdentityProviderResponse.Result, nil
 }
+
+// ListAccessIdentityProviderAuthContexts returns an identity provider's auth contexts
+// AzureAD only
+// Account API Reference: https://developers.cloudflare.com/api/operations/access-identity-providers-get-an-access-identity-provider
+// Zone API Reference: https://developers.cloudflare.com/api/operations/zone-level-access-identity-providers-get-an-access-identity-provider
+func (api *API) ListAccessIdentityProviderAuthContexts(ctx context.Context, rc *ResourceContainer, identityProviderID string) ([]AccessAuthContext, error) {
+	uri := fmt.Sprintf("/%s/%s/access/identity_providers/%s/auth_context", rc.Level, rc.Identifier, identityProviderID)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return []AccessAuthContext{}, err
+	}
+
+	var accessAuthContextListResponse AccessAuthContextsListResponse
+	err = json.Unmarshal(res, &accessAuthContextListResponse)
+	if err != nil {
+		return []AccessAuthContext{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return accessAuthContextListResponse.Result, nil
+}
+
+// UpdateAccessIdentityProviderAuthContexts updates an existing Access Identity
+// Provider.
+// AzureAD only
+// Account API Reference: https://developers.cloudflare.com/api/operations/access-identity-providers-refresh-an-access-identity-provider-auth-contexts
+// Zone API Reference: https://developers.cloudflare.com/api/operations/zone-level-access-identity-providers-update-an-access-identity-provider
+func (api *API) UpdateAccessIdentityProviderAuthContexts(ctx context.Context, rc *ResourceContainer, identityProviderID string) (AccessIdentityProvider, error) {
+	uri := fmt.Sprintf(
+		"/%s/%s/access/identity_providers/%s/auth_context",
+		rc.Level,
+		rc.Identifier,
+		identityProviderID,
+	)
+
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, nil)
+	if err != nil {
+		return AccessIdentityProvider{}, err
+	}
+
+	var accessIdentityProviderResponse AccessIdentityProviderResponse
+	err = json.Unmarshal(res, &accessIdentityProviderResponse)
+	if err != nil {
+		return AccessIdentityProvider{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return accessIdentityProviderResponse.Result, nil
+}
diff --git a/access_organization.go b/access_organization.go
index e0242b99d..d7f616cf5 100644
--- a/access_organization.go
+++ b/access_organization.go
@@ -19,7 +19,6 @@ type AccessOrganization struct {
 	UIReadOnlyToggleReason         string                        `json:"ui_read_only_toggle_reason,omitempty"`
 	UserSeatExpirationInactiveTime string                        `json:"user_seat_expiration_inactive_time,omitempty"`
 	AutoRedirectToIdentity         *bool                         `json:"auto_redirect_to_identity,omitempty"`
-	CustomPages                    AccessOrganizationCustomPages `json:"custom_pages,omitempty"`
 }
 
 // AccessOrganizationLoginDesign represents the login design options.
@@ -31,11 +30,6 @@ type AccessOrganizationLoginDesign struct {
 	FooterText      string `json:"footer_text"`
 }
 
-type AccessOrganizationCustomPages struct {
-	Forbidden      AccessCustomPageType `json:"forbidden,omitempty"`
-	IdentityDenied AccessCustomPageType `json:"identity_denied,omitempty"`
-}
-
 // AccessOrganizationListResponse represents the response from the list
 // access organization endpoint.
 type AccessOrganizationListResponse struct {
@@ -63,7 +57,6 @@ type CreateAccessOrganizationParams struct {
 	UIReadOnlyToggleReason         string                        `json:"ui_read_only_toggle_reason,omitempty"`
 	UserSeatExpirationInactiveTime string                        `json:"user_seat_expiration_inactive_time,omitempty"`
 	AutoRedirectToIdentity         *bool                         `json:"auto_redirect_to_identity,omitempty"`
-	CustomPages                    AccessOrganizationCustomPages `json:"custom_pages,omitempty"`
 }
 
 type UpdateAccessOrganizationParams struct {
@@ -74,7 +67,6 @@ type UpdateAccessOrganizationParams struct {
 	UIReadOnlyToggleReason         string                        `json:"ui_read_only_toggle_reason,omitempty"`
 	UserSeatExpirationInactiveTime string                        `json:"user_seat_expiration_inactive_time,omitempty"`
 	AutoRedirectToIdentity         *bool                         `json:"auto_redirect_to_identity,omitempty"`
-	CustomPages                    AccessOrganizationCustomPages `json:"custom_pages,omitempty"`
 }
 
 func (api *API) GetAccessOrganization(ctx context.Context, rc *ResourceContainer, params GetAccessOrganizationParams) (AccessOrganization, ResultInfo, error) {


Test patch:
diff --git a/access_application_test.go b/access_application_test.go
index 0309b6c74..3c07a5443 100644
--- a/access_application_test.go
+++ b/access_application_test.go
@@ -46,8 +46,7 @@ func TestAccessApplications(t *testing.T) {
 					"skip_interstitial": true,
 					"app_launcher_visible": true,
 					"service_auth_401_redirect": true,
-					"path_cookie_attribute": true,
-					"custom_pages": ["480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"]
+					"path_cookie_attribute": true
 				}
 			],
 			"result_info": {
@@ -84,7 +83,6 @@ func TestAccessApplications(t *testing.T) {
 		LogoURL:                 "https://www.example.com/example.png",
 		SkipInterstitial:        BoolPtr(true),
 		PathCookieAttribute:     BoolPtr(true),
-		CustomPages:             []string{"480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"},
 	}}
 
 	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", handler)
diff --git a/access_custom_page_test.go b/access_custom_page_test.go
deleted file mode 100644
index c81f034cd..000000000
--- a/access_custom_page_test.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestAccessCustomPages(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, r.Method, http.MethodGet, "HTTP method")
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			"success": true,
-			"errors": [],
-			"messages": [],
-			"result": [
-				{
-					"name": "Forbidden",
-					"app_count": 0,
-					"type": "forbidden",
-					"uid": "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"
-				}
-			],
-			"result_info": {
-				"page": 1,
-				"per_page": 20,
-				"count": 1,
-				"total_count": 1
-			}
-		}`)
-	}
-
-	want := []AccessCustomPage{
-		{
-			Name:     "Forbidden",
-			AppCount: 0,
-			Type:     Forbidden,
-			UID:      "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc",
-		},
-	}
-	mux.HandleFunc("/accounts/"+testAccountID+"/access/custom_pages", handler)
-	actual, err := client.ListAccessCustomPages(context.Background(), AccountIdentifier(testAccountID), ListAccessCustomPagesParams{})
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, actual)
-	}
-}
-
-func TestAccessCustomPage(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, r.Method, http.MethodGet, "HTTP method")
-		w.Header().Set("Content-Type", "application/json")
-		fmt.Fprintf(w, `{
-			"success": true,
-			"errors": [],
-			"messages": [],
-			"result": {
-				"custom_html": "<html><body><h1>Forbidden</h1></body></html>",
-				"name": "Forbidden",
-				"app_count": 0,
-				"type": "forbidden",
-				"uid": "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"
-			}
-		}`)
-	}
-
-	want := AccessCustomPage{
-		Name:       "Forbidden",
-		AppCount:   0,
-		Type:       Forbidden,
-		UID:        "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc",
-		CustomHTML: "<html><body><h1>Forbidden</h1></body></html>",
-	}
-	mux.HandleFunc("/accounts/"+testAccountID+"/access/custom_pages/480f4f69-1a28-4fdd-9240-1ed29f0ac1dc", handler)
-	actual, err := client.GetAccessCustomPage(context.Background(), AccountIdentifier(testAccountID), "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc")
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, actual)
-	}
-}
-
-func TestCreateAccessCustomPage(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, r.Method, http.MethodPost, "HTTP method")
-		w.Header().Set("Content-Type", "application/json")
-		fmt.Fprintf(w, `{
-			"success": true,
-			"errors": [],
-			"messages": [],
-			"result": {
-				"custom_html": "<html><body><h1>Forbidden</h1></body></html>",
-				"name": "Forbidden",
-				"app_count": 0,
-				"type": "forbidden",
-				"uid": "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"
-			}
-		}`)
-	}
-
-	customPage := AccessCustomPage{
-		Name:       "Forbidden",
-		AppCount:   0,
-		Type:       Forbidden,
-		UID:        "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc",
-		CustomHTML: "<html><body><h1>Forbidden</h1></body></html>",
-	}
-
-	mux.HandleFunc("/accounts/"+testAccountID+"/access/custom_pages", handler)
-	actual, err := client.CreateAccessCustomPage(context.Background(), AccountIdentifier(testAccountID), CreateAccessCustomPageParams{
-		Name:       "Forbidden",
-		Type:       Forbidden,
-		CustomHTML: "<html><body><h1>Forbidden</h1></body></html>",
-	})
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, customPage, actual)
-	}
-}
-
-func TestUpdateAccessCustomPage(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, r.Method, http.MethodPut, "HTTP method")
-		w.Header().Set("Content-Type", "application/json")
-		fmt.Fprintf(w, `{
-			"success": true,
-			"errors": [],
-			"messages": [],
-			"result": {
-				"custom_html": "<html><body><h1>Forbidden</h1></body></html>",
-				"name": "Forbidden",
-				"app_count": 0,
-				"type": "forbidden",
-				"uid": "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"
-			}
-		}`)
-	}
-
-	customPage := AccessCustomPage{
-		Name:       "Forbidden",
-		AppCount:   0,
-		Type:       Forbidden,
-		UID:        "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc",
-		CustomHTML: "<html><body><h1>Forbidden</h1></body></html>",
-	}
-
-	mux.HandleFunc("/accounts/"+testAccountID+"/access/custom_pages/480f4f69-1a28-4fdd-9240-1ed29f0ac1dc", handler)
-	actual, err := client.UpdateAccessCustomPage(context.Background(), AccountIdentifier(testAccountID), UpdateAccessCustomPageParams{
-		UID:        "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc",
-		Name:       "Forbidden",
-		Type:       Forbidden,
-		CustomHTML: "<html><body><h1>Forbidden</h1></body></html>",
-	})
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, customPage, actual)
-	}
-}
-
-func TestDeleteAccessCustomPage(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, r.Method, http.MethodDelete, "HTTP method")
-		w.Header().Set("Content-Type", "application/json")
-		fmt.Fprintf(w, `{
-			"success": true,
-			"errors": [],
-			"messages": [],
-			result: {
-				"id": "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"
-			}
-		}`)
-	}
-
-	mux.HandleFunc("/accounts/"+testAccountID+"/access/custom_pages/480f4f69-1a28-4fdd-9240-1ed29f0ac1dc", handler)
-	err := client.DeleteAccessCustomPage(context.Background(), AccountIdentifier(testAccountID), "480f4f69-1a28-4fdd-9240-1ed29f0ac1dc")
-
-	assert.NoError(t, err)
-}
diff --git a/access_identity_provider_test.go b/access_identity_provider_test.go
index ad9b6935b..e92854151 100644
--- a/access_identity_provider_test.go
+++ b/access_identity_provider_test.go
@@ -140,7 +140,8 @@ func TestCreateAccessIdentityProvider(t *testing.T) {
 				"type": "github",
 				"config": {
 					"client_id": "example_id",
-					"client_secret": "a-secret-key"
+					"client_secret": "a-secret-key",
+					"conditional_access_enabled": true
 				}
 			}
 		}
@@ -151,8 +152,9 @@ func TestCreateAccessIdentityProvider(t *testing.T) {
 		Name: "Widget Corps OTP",
 		Type: "github",
 		Config: AccessIdentityProviderConfiguration{
-			ClientID:     "example_id",
-			ClientSecret: "a-secret-key",
+			ClientID:                 "example_id",
+			ClientSecret:             "a-secret-key",
+			ConditionalAccessEnabled: true,
 		},
 	}
 
@@ -161,8 +163,9 @@ func TestCreateAccessIdentityProvider(t *testing.T) {
 		Name: "Widget Corps OTP",
 		Type: "github",
 		Config: AccessIdentityProviderConfiguration{
-			ClientID:     "example_id",
-			ClientSecret: "a-secret-key",
+			ClientID:                 "example_id",
+			ClientSecret:             "a-secret-key",
+			ConditionalAccessEnabled: true,
 		},
 	}
 
@@ -293,3 +296,121 @@ func TestDeleteAccessIdentityProvider(t *testing.T) {
 		assert.Equal(t, want, actual)
 	}
 }
+
+func TestListAccessIdentityProviderAuthContexts(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": [
+				{
+					"id": "04709095-568a-40c4-bf23-5d9edbefe21e",
+					"uid": "04709095-568a-40c4-bf23-5d9edbefe21e",
+					"ac_id": "c1",
+					"display_name": "test_c1",
+					"description": ""
+				},
+				{
+					"id": "a6c9b024-8fd1-48b7-9a05-8bca3a43f758",
+					"uid": "a6c9b024-8fd1-48b7-9a05-8bca3a43f758",
+					"ac_id": "c25",
+					"display_name": "test_c25",
+					"description": ""
+				}
+			]
+		}
+		`)
+	}
+
+	want := []AccessAuthContext{
+		{
+			ID:          "04709095-568a-40c4-bf23-5d9edbefe21e",
+			UID:         "04709095-568a-40c4-bf23-5d9edbefe21e",
+			ACID:        "c1",
+			DisplayName: "test_c1",
+			Description: "",
+		},
+		{
+			ID:          "a6c9b024-8fd1-48b7-9a05-8bca3a43f758",
+			UID:         "a6c9b024-8fd1-48b7-9a05-8bca3a43f758",
+			ACID:        "c25",
+			DisplayName: "test_c25",
+			Description: "",
+		},
+	}
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/identity_providers/f174e90a-fafe-4643-bbbc-4a0ed4fc8415/auth_context", handler)
+
+	actual, err := client.ListAccessIdentityProviderAuthContexts(context.Background(), testAccountRC, "f174e90a-fafe-4643-bbbc-4a0ed4fc8415")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/access/identity_providers/f174e90a-fafe-4643-bbbc-4a0ed4fc8415/auth_context", handler)
+
+	actual, err = client.ListAccessIdentityProviderAuthContexts(context.Background(), testAccountRC, "f174e90a-fafe-4643-bbbc-4a0ed4fc8415")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestUpdateAccessIdentityProviderAuthContext(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"id": "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+				"name": "Widget Corps",
+				"type": "AzureAD",
+				"config": {
+					"client_id": "example_id",
+					"client_secret": "a-secret-key",
+					"conditional_access_enabled": true
+				}
+			}
+		}
+		`)
+	}
+
+	want := AccessIdentityProvider{
+		ID:   "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name: "Widget Corps",
+		Type: "AzureAD",
+		Config: AccessIdentityProviderConfiguration{
+			ClientID:                 "example_id",
+			ClientSecret:             "a-secret-key",
+			ConditionalAccessEnabled: true,
+		},
+	}
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/identity_providers/f174e90a-fafe-4643-bbbc-4a0ed4fc8415/auth_context", handler)
+
+	actual, err := client.UpdateAccessIdentityProviderAuthContexts(context.Background(), testAccountRC, "f174e90a-fafe-4643-bbbc-4a0ed4fc8415")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/access/identity_providers/f174e90a-fafe-4643-bbbc-4a0ed4fc8415/auth_context", handler)
+
+	actual, err = client.UpdateAccessIdentityProviderAuthContexts(context.Background(), testAccountRC, "f174e90a-fafe-4643-bbbc-4a0ed4fc8415")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
diff --git a/access_policy_test.go b/access_policy_test.go
index e0c163722..045c45337 100644
--- a/access_policy_test.go
+++ b/access_policy_test.go
@@ -325,6 +325,141 @@ func TestCreateAccessPolicy(t *testing.T) {
 	}
 }
 
+func TestCreateAccessPolicyAuthContextRule(t *testing.T) {
+	setup()
+	defer teardown()
+	expectedAccessPolicyAuthContext := AccessPolicy{
+		ID:         "699d98642c564d2e855e9661899b7252",
+		Precedence: 1,
+		Decision:   "allow",
+		CreatedAt:  &createdAt,
+		UpdatedAt:  &updatedAt,
+		Name:       "Allow devs",
+		Include: []interface{}{
+			map[string]interface{}{"email": map[string]interface{}{"email": "test@example.com"}},
+		},
+		Exclude: []interface{}{},
+		Require: []interface{}{
+			map[string]interface{}{"auth_context": map[string]interface{}{"id": "authContextID123", "identity_provider_id": "IDPIDtest123", "ac_id": "c1"}},
+		},
+		IsolationRequired:            &isolationRequired,
+		PurposeJustificationRequired: &purposeJustificationRequired,
+		ApprovalRequired:             &approvalRequired,
+		PurposeJustificationPrompt:   &purposeJustificationPrompt,
+		ApprovalGroups: []AccessApprovalGroup{
+			{
+				EmailListUuid:   "2413b6d7-bbe5-48bd-8fbb-e52069c85561",
+				ApprovalsNeeded: 3,
+			},
+			{
+				EmailAddresses:  []string{"email1@example.com", "email2@example.com"},
+				ApprovalsNeeded: 1,
+			},
+		},
+	}
+
+	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": "699d98642c564d2e855e9661899b7252",
+				"precedence": 1,
+				"decision": "allow",
+				"created_at": "2014-01-01T05:20:00.12345Z",
+				"updated_at": "2014-01-01T05:20:00.12345Z",
+				"name": "Allow devs",
+				"include": [
+					{
+						"email": {
+							"email": "test@example.com"
+						}
+					}
+				],
+				"exclude": [],
+				"require": [
+					{
+						"auth_context": {
+							"id": "authContextID123",
+							"identity_provider_id": "IDPIDtest123",
+							"ac_id": "c1"
+						}
+					}
+				],
+				"isolation_required": true,
+				"purpose_justification_required": true,
+				"purpose_justification_prompt": "Please provide a business reason for your need to access before continuing.",
+				"approval_required": true,
+				"approval_groups": [
+					{
+						"email_list_uuid": "2413b6d7-bbe5-48bd-8fbb-e52069c85561",
+						"approvals_needed": 3
+					},
+					{
+						"email_addresses": ["email1@example.com", "email2@example.com"],
+						"approvals_needed": 1
+					}
+				]
+			}
+		}
+		`)
+	}
+
+	accessPolicy := CreateAccessPolicyParams{
+		ApplicationID: accessApplicationID,
+		Name:          "Allow devs",
+		Include: []interface{}{
+			AccessGroupEmail{struct {
+				Email string `json:"email"`
+			}{Email: "test@example.com"}},
+		},
+		Exclude: []interface{}{},
+		Require: []interface{}{
+			AccessGroupAzureAuthContext{struct {
+				ID                 string `json:"id"`
+				IdentityProviderID string `json:"identity_provider_id"`
+				ACID               string `json:"ac_id"`
+			}{
+				ID:                 "authContextID123",
+				IdentityProviderID: "IDPIDtest123",
+				ACID:               "c1",
+			}},
+		},
+		Decision:                     "allow",
+		PurposeJustificationRequired: &purposeJustificationRequired,
+		PurposeJustificationPrompt:   &purposeJustificationPrompt,
+		ApprovalGroups: []AccessApprovalGroup{
+			{
+				EmailListUuid:   "2413b6d7-bbe5-48bd-8fbb-e52069c85561",
+				ApprovalsNeeded: 3,
+			},
+			{
+				EmailAddresses:  []string{"email1@example.com", "email2@example.com"},
+				ApprovalsNeeded: 1,
+			},
+		},
+	}
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps/"+accessApplicationID+"/policies", handler)
+
+	actual, err := client.CreateAccessPolicy(context.Background(), testAccountRC, accessPolicy)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, expectedAccessPolicyAuthContext, actual)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/access/apps/"+accessApplicationID+"/policies", handler)
+
+	actual, err = client.CreateAccessPolicy(context.Background(), testZoneRC, accessPolicy)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, expectedAccessPolicyAuthContext, actual)
+	}
+}
+
 func TestUpdateAccessPolicy(t *testing.T) {
 	setup()
 	defer teardown()