Repository: cloudflare/cloudflare-go
Pull request #340 (2019-08-21 05:11:07)

Add support for Access Identity Providers
Updates the library to have full support for Access Identity Providers.

API reference: https://api.cloudflare.com/#access-identity-providers-properties
Identity provider reference: https://developers.cloudflare.com/access/configuring-identity-providers/

jacobbednarz: cc @deuill

Patch:
diff --git a/access_identity_provider.go b/access_identity_provider.go
new file mode 100644
index 000000000..b41ed8ff0
--- /dev/null
+++ b/access_identity_provider.go
@@ -0,0 +1,331 @@
+package cloudflare
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/pkg/errors"
+)
+
+// AccessIdentityProvider is the structure of the provider object.
+type AccessIdentityProvider struct {
+	ID     string      `json:"id,omitemtpy"`
+	Name   string      `json:"name"`
+	Type   string      `json:"type"`
+	Config interface{} `json:"config"`
+}
+
+// AccessAzureADConfiguration is the representation of the Azure AD identity
+// provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/azuread/
+type AccessAzureADConfiguration struct {
+	ClientID      string `json:"client_id"`
+	ClientSecret  string `json:"client_secret"`
+	DirectoryID   string `json:"directory_id"`
+	SupportGroups bool   `json:"support_groups"`
+}
+
+// AccessCentrifyConfiguration is the representation of the Centrify identity
+// provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/centrify/
+type AccessCentrifyConfiguration struct {
+	ClientID        string `json:"client_id"`
+	ClientSecret    string `json:"client_secret"`
+	CentrifyAccount string `json:"centrify_account"`
+	CentrifyAppID   string `json:"centrify_app_id"`
+}
+
+// AccessCentrifySAMLConfiguration is the representation of the Centrify
+// identity provider using SAML.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/saml-centrify/
+type AccessCentrifySAMLConfiguration struct {
+	IssuerURL          string   `json:"issuer_url"`
+	SsoTargetURL       string   `json:"sso_target_url"`
+	Attributes         []string `json:"attributes"`
+	EmailAttributeName string   `json:"email_attribute_name"`
+	SignRequest        bool     `json:"sign_request"`
+	IdpPublicCert      string   `json:"idp_public_cert"`
+}
+
+// AccessFacebookConfiguration is the representation of the Facebook identity
+// provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/facebook-login/
+type AccessFacebookConfiguration struct {
+	ClientID     string `json:"client_id"`
+	ClientSecret string `json:"client_secret"`
+}
+
+// AccessGSuiteConfiguration is the representation of the GSuite identity
+// provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/gsuite/
+type AccessGSuiteConfiguration struct {
+	ClientID     string `json:"client_id"`
+	ClientSecret string `json:"client_secret"`
+	AppsDomain   string `json:"apps_domain"`
+}
+
+// AccessGenericOIDCConfiguration is the representation of the generic OpenID
+// Connect (OIDC) connector.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/generic-oidc/
+type AccessGenericOIDCConfiguration struct {
+	ClientID     string `json:"client_id"`
+	ClientSecret string `json:"client_secret"`
+	AuthURL      string `json:"auth_url"`
+	TokenURL     string `json:"token_url"`
+	CertsURL     string `json:"certs_url"`
+}
+
+// AccessGitHubConfiguration is the representation of the GitHub identity
+// provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/github/
+type AccessGitHubConfiguration struct {
+	ClientID     string `json:"client_id"`
+	ClientSecret string `json:"client_secret"`
+}
+
+// AccessGoogleConfiguration is the representation of the Google identity
+// provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/google/
+type AccessGoogleConfiguration struct {
+	ClientID     string `json:"client_id"`
+	ClientSecret string `json:"client_secret"`
+}
+
+// AccessJumpCloudSAMLConfiguration is the representation of the Jump Cloud
+// identity provider using SAML.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/jumpcloud-saml/
+type AccessJumpCloudSAMLConfiguration struct {
+	IssuerURL          string   `json:"issuer_url"`
+	SsoTargetURL       string   `json:"sso_target_url"`
+	Attributes         []string `json:"attributes"`
+	EmailAttributeName string   `json:"email_attribute_name"`
+	SignRequest        bool     `json:"sign_request"`
+	IdpPublicCert      string   `json:"idp_public_cert"`
+}
+
+// AccessOktaConfiguration is the representation of the Okta identity provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/okta/
+type AccessOktaConfiguration struct {
+	ClientID     string `json:"client_id"`
+	ClientSecret string `json:"client_secret"`
+	OktaAccount  string `json:"okta_account"`
+}
+
+// AccessOktaSAMLConfiguration is the representation of the Okta identity
+// provider using SAML.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/saml-okta/
+type AccessOktaSAMLConfiguration struct {
+	IssuerURL          string   `json:"issuer_url"`
+	SsoTargetURL       string   `json:"sso_target_url"`
+	Attributes         []string `json:"attributes"`
+	EmailAttributeName string   `json:"email_attribute_name"`
+	SignRequest        bool     `json:"sign_request"`
+	IdpPublicCert      string   `json:"idp_public_cert"`
+}
+
+// AccessOneTimePinConfiguration is the representation of the default One Time
+// Pin identity provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/one-time-pin/
+type AccessOneTimePinConfiguration struct{}
+
+// AccessOneLoginOIDCConfiguration is the representation of the OneLogin
+// OpenID connector as an identity provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/onelogin-oidc/
+type AccessOneLoginOIDCConfiguration struct {
+	ClientID        string `json:"client_id"`
+	ClientSecret    string `json:"client_secret"`
+	OneloginAccount string `json:"onelogin_account"`
+}
+
+// AccessOneLoginSAMLConfiguration is the representation of the OneLogin
+// identity provider using SAML.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/onelogin-saml/
+type AccessOneLoginSAMLConfiguration struct {
+	IssuerURL          string   `json:"issuer_url"`
+	SsoTargetURL       string   `json:"sso_target_url"`
+	Attributes         []string `json:"attributes"`
+	EmailAttributeName string   `json:"email_attribute_name"`
+	SignRequest        bool     `json:"sign_request"`
+	IdpPublicCert      string   `json:"idp_public_cert"`
+}
+
+// AccessPingSAMLConfiguration is the representation of the Ping identity
+// provider using SAML.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/ping-saml/
+type AccessPingSAMLConfiguration struct {
+	IssuerURL          string   `json:"issuer_url"`
+	SsoTargetURL       string   `json:"sso_target_url"`
+	Attributes         []string `json:"attributes"`
+	EmailAttributeName string   `json:"email_attribute_name"`
+	SignRequest        bool     `json:"sign_request"`
+	IdpPublicCert      string   `json:"idp_public_cert"`
+}
+
+// AccessYandexConfiguration is the representation of the Yandex identity provider.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/yandex/
+type AccessYandexConfiguration struct {
+	ClientID     string `json:"client_id"`
+	ClientSecret string `json:"client_secret"`
+}
+
+// AccessADSAMLConfiguration is the representation of the Active Directory
+// identity provider using SAML.
+//
+// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/adfs/
+type AccessADSAMLConfiguration struct {
+	IssuerURL          string   `json:"issuer_url"`
+	SsoTargetURL       string   `json:"sso_target_url"`
+	Attributes         []string `json:"attributes"`
+	EmailAttributeName string   `json:"email_attribute_name"`
+	SignRequest        bool     `json:"sign_request"`
+	IdpPublicCert      string   `json:"idp_public_cert"`
+}
+
+// AccessIdentityProvidersListResponse is the API response for multiple
+// Access Identity Providers.
+type AccessIdentityProvidersListResponse struct {
+	Success  bool                     `json:"success"`
+	Errors   []string                 `json:"errors"`
+	Messages []string                 `json:"messages"`
+	Result   []AccessIdentityProvider `json:"result"`
+}
+
+// AccessIdentityProviderListResponse is the API response for a single
+// Access Identity Provider.
+type AccessIdentityProviderListResponse struct {
+	Success  bool                   `json:"success"`
+	Errors   []string               `json:"errors"`
+	Messages []string               `json:"messages"`
+	Result   AccessIdentityProvider `json:"result"`
+}
+
+// AccessIdentityProviders returns all Access Identity Providers for an
+// account.
+//
+// API reference: https://api.cloudflare.com/#access-identity-providers-list-access-identity-providers
+func (api *API) AccessIdentityProviders(accountID string) ([]AccessIdentityProvider, error) {
+	uri := "/accounts/" + accountID + "/access/identity_providers"
+
+	res, err := api.makeRequest("GET", uri, nil)
+	if err != nil {
+		return []AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessIdentityProviderResponse AccessIdentityProvidersListResponse
+	err = json.Unmarshal(res, &accessIdentityProviderResponse)
+	if err != nil {
+		return []AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessIdentityProviderResponse.Result, nil
+}
+
+// AccessIdentityProviderDetails returns a single Access Identity
+// Provider for an account.
+//
+// API reference: https://api.cloudflare.com/#access-identity-providers-access-identity-providers-details
+func (api *API) AccessIdentityProviderDetails(accountID, identityProviderID string) (AccessIdentityProvider, error) {
+	uri := fmt.Sprintf(
+		"/accounts/%s/access/identity_providers/%s",
+		accountID,
+		identityProviderID,
+	)
+
+	res, err := api.makeRequest("GET", uri, nil)
+	if err != nil {
+		return AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessIdentityProviderResponse AccessIdentityProviderListResponse
+	err = json.Unmarshal(res, &accessIdentityProviderResponse)
+	if err != nil {
+		return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessIdentityProviderResponse.Result, nil
+}
+
+// CreateAccessIdentityProvider creates a new Access Identity Provider.
+//
+// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider
+func (api *API) CreateAccessIdentityProvider(accountID string, identityProviderConfiguration AccessIdentityProvider) (AccessIdentityProvider, error) {
+	uri := "/accounts/" + accountID + "/access/identity_providers"
+
+	res, err := api.makeRequest("POST", uri, identityProviderConfiguration)
+	if err != nil {
+		return AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessIdentityProviderResponse AccessIdentityProviderListResponse
+	err = json.Unmarshal(res, &accessIdentityProviderResponse)
+	if err != nil {
+		return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessIdentityProviderResponse.Result, nil
+}
+
+// UpdateAccessIdentityProvider updates an existing Access Identity
+// Provider.
+//
+// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider
+func (api *API) UpdateAccessIdentityProvider(accountID, identityProviderUUID string, identityProviderConfiguration AccessIdentityProvider) (AccessIdentityProvider, error) {
+	uri := fmt.Sprintf(
+		"/accounts/%s/access/identity_providers/%s",
+		accountID,
+		identityProviderUUID,
+	)
+
+	res, err := api.makeRequest("PUT", uri, identityProviderConfiguration)
+	if err != nil {
+		return AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessIdentityProviderResponse AccessIdentityProviderListResponse
+	err = json.Unmarshal(res, &accessIdentityProviderResponse)
+	if err != nil {
+		return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessIdentityProviderResponse.Result, nil
+}
+
+// DeleteAccessIdentityProvider deletes an Access Identity Provider.
+//
+// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider
+func (api *API) DeleteAccessIdentityProvider(accountID, identityProviderUUID string) (AccessIdentityProvider, error) {
+	uri := fmt.Sprintf(
+		"/accounts/%s/access/identity_providers/%s",
+		accountID,
+		identityProviderUUID,
+	)
+
+	res, err := api.makeRequest("DELETE", uri, nil)
+	if err != nil {
+		return AccessIdentityProvider{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessIdentityProviderResponse AccessIdentityProviderListResponse
+	err = json.Unmarshal(res, &accessIdentityProviderResponse)
+	if err != nil {
+		return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessIdentityProviderResponse.Result, nil
+}
diff --git a/access_organization.go b/access_organization.go
new file mode 100644
index 000000000..5bc4a16aa
--- /dev/null
+++ b/access_organization.go
@@ -0,0 +1,101 @@
+package cloudflare
+
+import (
+	"encoding/json"
+	"time"
+
+	"github.com/pkg/errors"
+)
+
+// AccessOrganization represents an Access organization.
+type AccessOrganization struct {
+	CreatedAt   *time.Time                    `json:"created_at"`
+	UpdatedAt   *time.Time                    `json:"updated_at"`
+	Name        string                        `json:"name"`
+	AuthDomain  string                        `json:"auth_domain"`
+	LoginDesign AccessOrganizationLoginDesign `json:"login_design"`
+}
+
+// AccessOrganizationLoginDesign represents the login design options.
+type AccessOrganizationLoginDesign struct {
+	BackgroundColor string `json:"background_color"`
+	TextColor       string `json:"text_color"`
+	LogoPath        string `json:"logo_path"`
+}
+
+// AccessOrganizationListResponse represents the response from the list
+// access organization endpoint.
+type AccessOrganizationListResponse struct {
+	Result AccessOrganization `json:"result"`
+	Response
+	ResultInfo `json:"result_info"`
+}
+
+// AccessOrganizationDetailResponse is the API response, containing a
+// single access organization.
+type AccessOrganizationDetailResponse struct {
+	Success  bool               `json:"success"`
+	Errors   []string           `json:"errors"`
+	Messages []string           `json:"messages"`
+	Result   AccessOrganization `json:"result"`
+}
+
+// AccessOrganization returns the Access organisation details.
+//
+// API reference: https://api.cloudflare.com/#access-organizations-access-organization-details
+func (api *API) AccessOrganization(accountID string) (AccessOrganization, ResultInfo, error) {
+	uri := "/accounts/" + accountID + "/access/organizations"
+
+	res, err := api.makeRequest("GET", uri, nil)
+	if err != nil {
+		return AccessOrganization{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessOrganizationListResponse AccessOrganizationListResponse
+	err = json.Unmarshal(res, &accessOrganizationListResponse)
+	if err != nil {
+		return AccessOrganization{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessOrganizationListResponse.Result, accessOrganizationListResponse.ResultInfo, nil
+}
+
+// CreateAccessOrganization creates the Access organisation details.
+//
+// API reference: https://api.cloudflare.com/#access-organizations-create-access-organization
+func (api *API) CreateAccessOrganization(accountID string, accessOrganization AccessOrganization) (AccessOrganization, error) {
+	uri := "/accounts/" + accountID + "/access/organizations"
+
+	res, err := api.makeRequest("POST", uri, accessOrganization)
+	if err != nil {
+		return AccessOrganization{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessOrganizationDetailResponse AccessOrganizationDetailResponse
+	err = json.Unmarshal(res, &accessOrganizationDetailResponse)
+	if err != nil {
+		return AccessOrganization{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessOrganizationDetailResponse.Result, nil
+}
+
+// UpdateAccessOrganization creates the Access organisation details.
+//
+// API reference: https://api.cloudflare.com/#access-organizations-update-access-organization
+func (api *API) UpdateAccessOrganization(accountID string, accessOrganization AccessOrganization) (AccessOrganization, error) {
+	uri := "/accounts/" + accountID + "/access/organizations"
+
+	res, err := api.makeRequest("PUT", uri, accessOrganization)
+	if err != nil {
+		return AccessOrganization{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessOrganizationDetailResponse AccessOrganizationDetailResponse
+	err = json.Unmarshal(res, &accessOrganizationDetailResponse)
+	if err != nil {
+		return AccessOrganization{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessOrganizationDetailResponse.Result, nil
+}
diff --git a/access_service_tokens.go b/access_service_tokens.go
new file mode 100644
index 000000000..66a2bb794
--- /dev/null
+++ b/access_service_tokens.go
@@ -0,0 +1,167 @@
+package cloudflare
+
+import (
+	"encoding/json"
+	"fmt"
+	"time"
+
+	"github.com/pkg/errors"
+)
+
+// AccessServiceToken represents an Access Service Token.
+type AccessServiceToken struct {
+	ClientID  string     `json:"client_id"`
+	CreatedAt *time.Time `json:"created_at"`
+	ExpiresAt *time.Time `json:"expires_at"`
+	ID        string     `json:"id"`
+	Name      string     `json:"name"`
+	UpdatedAt *time.Time `json:"updated_at"`
+}
+
+// AccessServiceTokenUpdateResponse represents the response from the API
+// when a new Service Token is updated. This base struct is also used in the
+// Create as they are very similar responses.
+type AccessServiceTokenUpdateResponse struct {
+	CreatedAt *time.Time `json:"created_at"`
+	UpdatedAt *time.Time `json:"updated_at"`
+	ID        string     `json:"id"`
+	Name      string     `json:"name"`
+	ClientID  string     `json:"client_id"`
+}
+
+// AccessServiceTokenCreateResponse is the same API response as the Update
+// operation with the exception that the `ClientSecret` is present in a
+// Create operation.
+type AccessServiceTokenCreateResponse struct {
+	CreatedAt    *time.Time `json:"created_at"`
+	UpdatedAt    *time.Time `json:"updated_at"`
+	ID           string     `json:"id"`
+	Name         string     `json:"name"`
+	ClientID     string     `json:"client_id"`
+	ClientSecret string     `json:"client_secret"`
+}
+
+// AccessServiceTokensListResponse represents the response from the list
+// Access Service Tokens endpoint.
+type AccessServiceTokensListResponse struct {
+	Result []AccessServiceToken `json:"result"`
+	Response
+	ResultInfo `json:"result_info"`
+}
+
+// AccessServiceTokensDetailResponse is the API response, containing a single
+// Access Service Token.
+type AccessServiceTokensDetailResponse struct {
+	Success  bool               `json:"success"`
+	Errors   []string           `json:"errors"`
+	Messages []string           `json:"messages"`
+	Result   AccessServiceToken `json:"result"`
+}
+
+// AccessServiceTokensCreationDetailResponse is the API response, containing a
+// single Access Service Token.
+type AccessServiceTokensCreationDetailResponse struct {
+	Success  bool                             `json:"success"`
+	Errors   []string                         `json:"errors"`
+	Messages []string                         `json:"messages"`
+	Result   AccessServiceTokenCreateResponse `json:"result"`
+}
+
+// AccessServiceTokensUpdateDetailResponse is the API response, containing a
+// single Access Service Token.
+type AccessServiceTokensUpdateDetailResponse struct {
+	Success  bool                             `json:"success"`
+	Errors   []string                         `json:"errors"`
+	Messages []string                         `json:"messages"`
+	Result   AccessServiceTokenUpdateResponse `json:"result"`
+}
+
+// AccessServiceTokens returns all Access Service Tokens for an account.
+//
+// API reference: https://api.cloudflare.com/#access-service-tokens-list-access-service-tokens
+func (api *API) AccessServiceTokens(accountID string) ([]AccessServiceToken, ResultInfo, error) {
+	uri := "/accounts/" + accountID + "/access/service_tokens"
+
+	res, err := api.makeRequest("GET", uri, nil)
+	if err != nil {
+		return []AccessServiceToken{}, ResultInfo{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessServiceTokensListResponse AccessServiceTokensListResponse
+	err = json.Unmarshal(res, &accessServiceTokensListResponse)
+	if err != nil {
+		return []AccessServiceToken{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessServiceTokensListResponse.Result, accessServiceTokensListResponse.ResultInfo, nil
+}
+
+// CreateAccessServiceToken creates a new Access Service Token for an account.
+//
+// API reference: https://api.cloudflare.com/#access-service-tokens-create-access-service-token
+func (api *API) CreateAccessServiceToken(accountID, name string) (AccessServiceTokenCreateResponse, error) {
+	uri := "/accounts/" + accountID + "/access/service_tokens"
+	marshalledName, _ := json.Marshal(struct {
+		Name string `json:"name"`
+	}{name})
+
+	res, err := api.makeRequest("POST", uri, marshalledName)
+
+	if err != nil {
+		return AccessServiceTokenCreateResponse{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessServiceTokenCreation AccessServiceTokensCreationDetailResponse
+	err = json.Unmarshal(res, &accessServiceTokenCreation)
+	if err != nil {
+		return AccessServiceTokenCreateResponse{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessServiceTokenCreation.Result, nil
+}
+
+// UpdateAccessServiceToken updates an existing Access Service Token for an
+// account.
+//
+// API reference: https://api.cloudflare.com/#access-service-tokens-update-access-service-token
+func (api *API) UpdateAccessServiceToken(accountID, uuid, name string) (AccessServiceTokenUpdateResponse, error) {
+	uri := fmt.Sprintf("/accounts/%s/access/service_tokens/%s", accountID, uuid)
+
+	marshalledName, _ := json.Marshal(struct {
+		Name string `json:"name"`
+	}{name})
+
+	res, err := api.makeRequest("PUT", uri, marshalledName)
+	if err != nil {
+		return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessServiceTokenUpdate AccessServiceTokensUpdateDetailResponse
+	err = json.Unmarshal(res, &accessServiceTokenUpdate)
+	if err != nil {
+		return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessServiceTokenUpdate.Result, nil
+}
+
+// DeleteAccessServiceToken removes an existing Access Service Token for an
+// account.
+//
+// API reference: https://api.cloudflare.com/#access-service-tokens-delete-access-service-token
+func (api *API) DeleteAccessServiceToken(accountID, uuid string) (AccessServiceTokenUpdateResponse, error) {
+	uri := fmt.Sprintf("/accounts/%s/access/service_tokens/%s", accountID, uuid)
+
+	res, err := api.makeRequest("DELETE", uri, nil)
+	if err != nil {
+		return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errMakeRequestError)
+	}
+
+	var accessServiceTokenUpdate AccessServiceTokensUpdateDetailResponse
+	err = json.Unmarshal(res, &accessServiceTokenUpdate)
+	if err != nil {
+		return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errUnmarshalError)
+	}
+
+	return accessServiceTokenUpdate.Result, nil
+}


Test patch:
diff --git a/access_identity_provider_test.go b/access_identity_provider_test.go
new file mode 100644
index 000000000..5f465a78c
--- /dev/null
+++ b/access_identity_provider_test.go
@@ -0,0 +1,243 @@
+package cloudflare
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAccessIdentityProviders(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "GET", "Expected method 'GET', 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 OTP",
+					"type": "github",
+					"config": {
+						"client_id": "example_id",
+						"client_secret": "a-secret-key"
+					}
+				}
+			]
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/identity_providers", handler)
+
+	want := []AccessIdentityProvider{AccessIdentityProvider{
+		ID:   "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name: "Widget Corps OTP",
+		Type: "github",
+		Config: map[string]interface{}{
+			"client_id":     "example_id",
+			"client_secret": "a-secret-key",
+		},
+	}}
+
+	actual, err := client.AccessIdentityProviders("01a7362d577a6c3019a474fd6f485823")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestAccessIdentityProviderDetails(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "GET", "Expected method 'GET', 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 OTP",
+				"type": "github",
+				"config": {
+					"client_id": "example_id",
+					"client_secret": "a-secret-key"
+				}
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/identity_providers/f174e90a-fafe-4643-bbbc-4a0ed4fc841", handler)
+
+	want := AccessIdentityProvider{
+		ID:   "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name: "Widget Corps OTP",
+		Type: "github",
+		Config: map[string]interface{}{
+			"client_id":     "example_id",
+			"client_secret": "a-secret-key",
+		},
+	}
+
+	actual, err := client.AccessIdentityProviderDetails("01a7362d577a6c3019a474fd6f485823", "f174e90a-fafe-4643-bbbc-4a0ed4fc841")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestCreateAccessIdentityProvider(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "POST", "Expected method 'POST', 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 OTP",
+				"type": "github",
+				"config": {
+					"client_id": "example_id",
+					"client_secret": "a-secret-key"
+				}
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/identity_providers", handler)
+
+	newIdentityProvider := AccessIdentityProvider{
+		Name: "Widget Corps OTP",
+		Type: "github",
+		Config: AccessGitHubConfiguration{
+			ClientID:     "example_id",
+			ClientSecret: "a-secret-key",
+		},
+	}
+
+	want := AccessIdentityProvider{
+		ID:   "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name: "Widget Corps OTP",
+		Type: "github",
+		Config: map[string]interface{}{
+			"client_id":     "example_id",
+			"client_secret": "a-secret-key",
+		},
+	}
+
+	actual, err := client.CreateAccessIdentityProvider("01a7362d577a6c3019a474fd6f485823", newIdentityProvider)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+func TestUpdateAccessIdentityProvider(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "PUT", "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 OTP",
+				"type": "github",
+				"config": {
+					"client_id": "example_id",
+					"client_secret": "a-secret-key"
+				}
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/identity_providers/f174e90a-fafe-4643-bbbc-4a0ed4fc8415", handler)
+
+	updatedIdentityProvider := AccessIdentityProvider{
+		Name: "Widget Corps OTP",
+		Type: "github",
+		Config: AccessGitHubConfiguration{
+			ClientID:     "example_id",
+			ClientSecret: "a-secret-key",
+		},
+	}
+
+	want := AccessIdentityProvider{
+		ID:   "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name: "Widget Corps OTP",
+		Type: "github",
+		Config: map[string]interface{}{
+			"client_id":     "example_id",
+			"client_secret": "a-secret-key",
+		},
+	}
+
+	actual, err := client.UpdateAccessIdentityProvider("01a7362d577a6c3019a474fd6f485823", "f174e90a-fafe-4643-bbbc-4a0ed4fc8415", updatedIdentityProvider)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestDeleteAccessIdentityProvider(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "DELETE", "Expected method 'DELETE', 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 OTP",
+				"type": "github",
+				"config": {
+					"client_id": "example_id",
+					"client_secret": "a-secret-key"
+				}
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/identity_providers/f174e90a-fafe-4643-bbbc-4a0ed4fc8415", handler)
+
+	want := AccessIdentityProvider{
+		ID:   "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name: "Widget Corps OTP",
+		Type: "github",
+		Config: map[string]interface{}{
+			"client_id":     "example_id",
+			"client_secret": "a-secret-key",
+		},
+	}
+
+	actual, err := client.DeleteAccessIdentityProvider("01a7362d577a6c3019a474fd6f485823", "f174e90a-fafe-4643-bbbc-4a0ed4fc8415")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
diff --git a/access_organization_test.go b/access_organization_test.go
new file mode 100644
index 000000000..cc6c11a72
--- /dev/null
+++ b/access_organization_test.go
@@ -0,0 +1,157 @@
+package cloudflare
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAccessOrganization(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"created_at": "2014-01-01T05:20:00.12345Z",
+				"updated_at": "2014-01-01T05:20:00.12345Z",
+				"name": "Widget Corps Internal Applications",
+				"auth_domain": "test.cloudflareaccess.com",
+				"login_design": {
+					"background_color": "#c5ed1b",
+					"text_color": "#c5ed1b",
+					"logo_path": "https://example.com/logo.png"
+				}
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/organizations", handler)
+	createdAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
+	updatedAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
+
+	want := AccessOrganization{
+		Name:       "Widget Corps Internal Applications",
+		CreatedAt:  &createdAt,
+		UpdatedAt:  &updatedAt,
+		AuthDomain: "test.cloudflareaccess.com",
+		LoginDesign: AccessOrganizationLoginDesign{
+			BackgroundColor: "#c5ed1b",
+			TextColor:       "#c5ed1b",
+			LogoPath:        "https://example.com/logo.png",
+		},
+	}
+
+	actual, _, err := client.AccessOrganization("01a7362d577a6c3019a474fd6f485823")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestCreateAccessOrganization(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "POST", "Expected method 'POST', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"created_at": "2014-01-01T05:20:00.12345Z",
+				"updated_at": "2014-01-01T05:20:00.12345Z",
+				"name": "Widget Corps Internal Applications",
+				"auth_domain": "test.cloudflareaccess.com",
+				"login_design": {
+					"background_color": "#c5ed1b",
+					"text_color": "#c5ed1b",
+					"logo_path": "https://example.com/logo.png"
+				}
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/organizations", handler)
+	createdAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
+	updatedAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
+
+	want := AccessOrganization{
+		Name:       "Widget Corps Internal Applications",
+		CreatedAt:  &createdAt,
+		UpdatedAt:  &updatedAt,
+		AuthDomain: "test.cloudflareaccess.com",
+		LoginDesign: AccessOrganizationLoginDesign{
+			BackgroundColor: "#c5ed1b",
+			TextColor:       "#c5ed1b",
+			LogoPath:        "https://example.com/logo.png",
+		},
+	}
+
+	actual, err := client.CreateAccessOrganization("01a7362d577a6c3019a474fd6f485823", want)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestUpdateAccessOrganization(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "PUT", "Expected method 'PUT', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"created_at": "2014-01-01T05:20:00.12345Z",
+				"updated_at": "2014-01-01T05:20:00.12345Z",
+				"name": "Widget Corps Internal Applications",
+				"auth_domain": "test.cloudflareaccess.com",
+				"login_design": {
+					"background_color": "#c5ed1b",
+					"text_color": "#c5ed1b",
+					"logo_path": "https://example.com/logo.png"
+				}
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/organizations", handler)
+	createdAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
+	updatedAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
+
+	want := AccessOrganization{
+		Name:       "Widget Corps Internal Applications",
+		CreatedAt:  &createdAt,
+		UpdatedAt:  &updatedAt,
+		AuthDomain: "test.cloudflareaccess.com",
+		LoginDesign: AccessOrganizationLoginDesign{
+			BackgroundColor: "#c5ed1b",
+			TextColor:       "#c5ed1b",
+			LogoPath:        "https://example.com/logo.png",
+		},
+	}
+
+	actual, err := client.UpdateAccessOrganization("01a7362d577a6c3019a474fd6f485823", want)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
diff --git a/access_service_tokens_test.go b/access_service_tokens_test.go
new file mode 100644
index 000000000..a6097013c
--- /dev/null
+++ b/access_service_tokens_test.go
@@ -0,0 +1,179 @@
+package cloudflare
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAccessServiceTokens(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "GET", "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": [
+				{
+					"created_at": "2014-01-01T05:20:00.12345Z",
+					"updated_at": "2014-01-01T05:20:00.12345Z",
+					"id": "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+					"name": "CI/CD token",
+					"client_id": "88bf3b6d86161464f6509f7219099e57.access.example.com"
+				}
+			]
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/service_tokens", handler)
+	createdAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
+	updatedAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z")
+
+	want := []AccessServiceToken{AccessServiceToken{
+		CreatedAt: &createdAt,
+		UpdatedAt: &updatedAt,
+		ID:        "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name:      "CI/CD token",
+		ClientID:  "88bf3b6d86161464f6509f7219099e57.access.example.com",
+	}}
+
+	actual, _, err := client.AccessServiceTokens("01a7362d577a6c3019a474fd6f485823")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestCreateAccessServiceToken(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "POST", "Expected method 'POST', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"created_at": "2014-01-01T05:20:00.12345Z",
+				"updated_at": "2014-01-01T05:20:00.12345Z",
+				"id": "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+				"name": "CI/CD token",
+				"client_id": "88bf3b6d86161464f6509f7219099e57.access.example.com",
+				"client_secret": "bdd31cbc4dec990953e39163fbbb194c93313ca9f0a6e420346af9d326b1d2a5"
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/service_tokens", handler)
+
+	expected := AccessServiceTokenCreateResponse{
+		CreatedAt:    &createdAt,
+		UpdatedAt:    &updatedAt,
+		ID:           "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name:         "CI/CD token",
+		ClientID:     "88bf3b6d86161464f6509f7219099e57.access.example.com",
+		ClientSecret: "bdd31cbc4dec990953e39163fbbb194c93313ca9f0a6e420346af9d326b1d2a5",
+	}
+
+	actual, err := client.CreateAccessServiceToken("01a7362d577a6c3019a474fd6f485823", "CI/CD token")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, expected, actual)
+	}
+}
+
+func TestUpdateAccessServiceToken(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "PUT", "Expected method 'PUT', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"created_at": "2014-01-01T05:20:00.12345Z",
+				"updated_at": "2014-01-01T05:20:00.12345Z",
+				"id": "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+				"name": "CI/CD token",
+				"client_id": "88bf3b6d86161464f6509f7219099e57.access.example.com"
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/service_tokens/f174e90a-fafe-4643-bbbc-4a0ed4fc8415", handler)
+
+	expected := AccessServiceTokenUpdateResponse{
+		CreatedAt: &createdAt,
+		UpdatedAt: &updatedAt,
+		ID:        "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name:      "CI/CD token",
+		ClientID:  "88bf3b6d86161464f6509f7219099e57.access.example.com",
+	}
+
+	actual, err := client.UpdateAccessServiceToken(
+		"01a7362d577a6c3019a474fd6f485823",
+		"f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		"CI/CD token",
+	)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, expected, actual)
+	}
+}
+
+func TestDeleteAccessServiceToken(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, "DELETE", "Expected method 'DELETE', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"created_at": "2014-01-01T05:20:00.12345Z",
+				"updated_at": "2014-01-01T05:20:00.12345Z",
+				"id": "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+				"name": "CI/CD token",
+				"client_id": "88bf3b6d86161464f6509f7219099e57.access.example.com"
+			}
+		}
+		`)
+	}
+
+	mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/access/service_tokens/f174e90a-fafe-4643-bbbc-4a0ed4fc8415", handler)
+
+	expected := AccessServiceTokenUpdateResponse{
+		CreatedAt: &createdAt,
+		UpdatedAt: &updatedAt,
+		ID:        "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+		Name:      "CI/CD token",
+		ClientID:  "88bf3b6d86161464f6509f7219099e57.access.example.com",
+	}
+
+	actual, err := client.DeleteAccessServiceToken(
+		"01a7362d577a6c3019a474fd6f485823",
+		"f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
+	)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, expected, actual)
+	}
+}