dnscontrol/pkg/cloudflare-go/access_application.go
Tom Limoncelli 7fd6a74e0c
CLOUDFLAREAPI: CF_REDIRECT/CF_TEMP_REDIRECT should dtrt using Single Redirects (#3002)
Co-authored-by: Josh Zhang <jzhang1@stackoverflow.com>
2024-06-18 17:38:50 -04:00

510 lines
24 KiB
Go

package cloudflare
import (
"context"
"errors"
"fmt"
"net/http"
"time"
"github.com/goccy/go-json"
)
// AccessApplicationType represents the application type.
type AccessApplicationType string
// These constants represent all valid application types.
const (
SelfHosted AccessApplicationType = "self_hosted"
SSH AccessApplicationType = "ssh"
VNC AccessApplicationType = "vnc"
Biso AccessApplicationType = "biso"
AppLauncher AccessApplicationType = "app_launcher"
Warp AccessApplicationType = "warp"
Bookmark AccessApplicationType = "bookmark"
Saas AccessApplicationType = "saas"
)
// AccessApplication represents an Access application.
type AccessApplication struct {
GatewayRules []AccessApplicationGatewayRule `json:"gateway_rules,omitempty"`
AllowedIdps []string `json:"allowed_idps,omitempty"`
CustomDenyMessage string `json:"custom_deny_message,omitempty"`
LogoURL string `json:"logo_url,omitempty"`
AUD string `json:"aud,omitempty"`
Domain string `json:"domain"`
SelfHostedDomains []string `json:"self_hosted_domains"`
Type AccessApplicationType `json:"type,omitempty"`
SessionDuration string `json:"session_duration,omitempty"`
SameSiteCookieAttribute string `json:"same_site_cookie_attribute,omitempty"`
CustomDenyURL string `json:"custom_deny_url,omitempty"`
CustomNonIdentityDenyURL string `json:"custom_non_identity_deny_url,omitempty"`
Name string `json:"name"`
ID string `json:"id,omitempty"`
PrivateAddress string `json:"private_address"`
CorsHeaders *AccessApplicationCorsHeaders `json:"cors_headers,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
SaasApplication *SaasApplication `json:"saas_app,omitempty"`
AutoRedirectToIdentity *bool `json:"auto_redirect_to_identity,omitempty"`
SkipInterstitial *bool `json:"skip_interstitial,omitempty"`
AppLauncherVisible *bool `json:"app_launcher_visible,omitempty"`
EnableBindingCookie *bool `json:"enable_binding_cookie,omitempty"`
HttpOnlyCookieAttribute *bool `json:"http_only_cookie_attribute,omitempty"`
ServiceAuth401Redirect *bool `json:"service_auth_401_redirect,omitempty"`
PathCookieAttribute *bool `json:"path_cookie_attribute,omitempty"`
AllowAuthenticateViaWarp *bool `json:"allow_authenticate_via_warp,omitempty"`
OptionsPreflightBypass *bool `json:"options_preflight_bypass,omitempty"`
CustomPages []string `json:"custom_pages,omitempty"`
Tags []string `json:"tags,omitempty"`
SCIMConfig *AccessApplicationSCIMConfig `json:"scim_config,omitempty"`
Policies []AccessPolicy `json:"policies,omitempty"`
AccessAppLauncherCustomization
}
type AccessApplicationGatewayRule struct {
ID string `json:"id,omitempty"`
}
// AccessApplicationCorsHeaders represents the CORS HTTP headers for an Access
// Application.
type AccessApplicationCorsHeaders struct {
AllowedMethods []string `json:"allowed_methods,omitempty"`
AllowedOrigins []string `json:"allowed_origins,omitempty"`
AllowedHeaders []string `json:"allowed_headers,omitempty"`
AllowAllMethods bool `json:"allow_all_methods,omitempty"`
AllowAllHeaders bool `json:"allow_all_headers,omitempty"`
AllowAllOrigins bool `json:"allow_all_origins,omitempty"`
AllowCredentials bool `json:"allow_credentials,omitempty"`
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"`
}
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)
}
func (a *AccessApplicationScimAuthenticationJson) MarshalJSON() ([]byte, error) {
return json.Marshal(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 {
Result []AccessApplication `json:"result"`
Response
ResultInfo `json:"result_info"`
}
// AccessApplicationDetailResponse is the API response, containing a single
// access application.
type AccessApplicationDetailResponse struct {
Success bool `json:"success"`
Errors []string `json:"errors"`
Messages []string `json:"messages"`
Result AccessApplication `json:"result"`
}
type SourceConfig struct {
Name string `json:"name,omitempty"`
NameByIDP map[string]string `json:"name_by_idp,omitempty"`
}
type SAMLAttributeConfig struct {
Name string `json:"name,omitempty"`
NameFormat string `json:"name_format,omitempty"`
FriendlyName string `json:"friendly_name,omitempty"`
Required bool `json:"required,omitempty"`
Source SourceConfig `json:"source"`
}
type OIDCClaimConfig struct {
Name string `json:"name,omitempty"`
Source SourceConfig `json:"source"`
Required *bool `json:"required,omitempty"`
Scope string `json:"scope,omitempty"`
}
type RefreshTokenOptions struct {
Lifetime string `json:"lifetime,omitempty"`
}
type AccessApplicationHybridAndImplicitOptions struct {
ReturnIDTokenFromAuthorizationEndpoint *bool `json:"return_id_token_from_authorization_endpoint,omitempty"`
ReturnAccessTokenFromAuthorizationEndpoint *bool `json:"return_access_token_from_authorization_endpoint,omitempty"`
}
type SaasApplication struct {
// Items common to both SAML and OIDC
AppID string `json:"app_id,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
PublicKey string `json:"public_key,omitempty"`
AuthType string `json:"auth_type,omitempty"`
// SAML saas app
ConsumerServiceUrl string `json:"consumer_service_url,omitempty"`
SPEntityID string `json:"sp_entity_id,omitempty"`
IDPEntityID string `json:"idp_entity_id,omitempty"`
NameIDFormat string `json:"name_id_format,omitempty"`
SSOEndpoint string `json:"sso_endpoint,omitempty"`
DefaultRelayState string `json:"default_relay_state,omitempty"`
CustomAttributes []SAMLAttributeConfig `json:"custom_attributes,omitempty"`
NameIDTransformJsonata string `json:"name_id_transform_jsonata,omitempty"`
SamlAttributeTransformJsonata string `json:"saml_attribute_transform_jsonata"`
// OIDC saas app
ClientID string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
GrantTypes []string `json:"grant_types,omitempty"`
Scopes []string `json:"scopes,omitempty"`
AppLauncherURL string `json:"app_launcher_url,omitempty"`
GroupFilterRegex string `json:"group_filter_regex,omitempty"`
CustomClaims []OIDCClaimConfig `json:"custom_claims,omitempty"`
AllowPKCEWithoutClientSecret *bool `json:"allow_pkce_without_client_secret,omitempty"`
RefreshTokenOptions *RefreshTokenOptions `json:"refresh_token_options,omitempty"`
HybridAndImplicitOptions *AccessApplicationHybridAndImplicitOptions `json:"hybrid_and_implicit_options,omitempty"`
AccessTokenLifetime string `json:"access_token_lifetime,omitempty"`
}
type AccessAppLauncherCustomization struct {
LandingPageDesign AccessLandingPageDesign `json:"landing_page_design"`
LogoURL string `json:"app_launcher_logo_url"`
HeaderBackgroundColor string `json:"header_bg_color"`
BackgroundColor string `json:"bg_color"`
FooterLinks []AccessFooterLink `json:"footer_links"`
}
type AccessFooterLink struct {
Name string `json:"name"`
URL string `json:"url"`
}
type AccessLandingPageDesign struct {
Title string `json:"title"`
Message string `json:"message"`
ImageURL string `json:"image_url"`
ButtonColor string `json:"button_color"`
ButtonTextColor string `json:"button_text_color"`
}
type ListAccessApplicationsParams struct {
ResultInfo
}
type CreateAccessApplicationParams struct {
AllowedIdps []string `json:"allowed_idps,omitempty"`
AppLauncherVisible *bool `json:"app_launcher_visible,omitempty"`
AUD string `json:"aud,omitempty"`
AutoRedirectToIdentity *bool `json:"auto_redirect_to_identity,omitempty"`
CorsHeaders *AccessApplicationCorsHeaders `json:"cors_headers,omitempty"`
CustomDenyMessage string `json:"custom_deny_message,omitempty"`
CustomDenyURL string `json:"custom_deny_url,omitempty"`
CustomNonIdentityDenyURL string `json:"custom_non_identity_deny_url,omitempty"`
Domain string `json:"domain"`
EnableBindingCookie *bool `json:"enable_binding_cookie,omitempty"`
GatewayRules []AccessApplicationGatewayRule `json:"gateway_rules,omitempty"`
HttpOnlyCookieAttribute *bool `json:"http_only_cookie_attribute,omitempty"`
LogoURL string `json:"logo_url,omitempty"`
Name string `json:"name"`
PathCookieAttribute *bool `json:"path_cookie_attribute,omitempty"`
PrivateAddress string `json:"private_address"`
SaasApplication *SaasApplication `json:"saas_app,omitempty"`
SameSiteCookieAttribute string `json:"same_site_cookie_attribute,omitempty"`
SelfHostedDomains []string `json:"self_hosted_domains"`
ServiceAuth401Redirect *bool `json:"service_auth_401_redirect,omitempty"`
SessionDuration string `json:"session_duration,omitempty"`
SkipInterstitial *bool `json:"skip_interstitial,omitempty"`
OptionsPreflightBypass *bool `json:"options_preflight_bypass,omitempty"`
Type AccessApplicationType `json:"type,omitempty"`
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"`
// List of policy ids to link to this application in ascending order of precedence.
Policies []string `json:"policies,omitempty"`
AccessAppLauncherCustomization
}
type UpdateAccessApplicationParams struct {
ID string `json:"id,omitempty"`
AllowedIdps []string `json:"allowed_idps,omitempty"`
AppLauncherVisible *bool `json:"app_launcher_visible,omitempty"`
AUD string `json:"aud,omitempty"`
AutoRedirectToIdentity *bool `json:"auto_redirect_to_identity,omitempty"`
CorsHeaders *AccessApplicationCorsHeaders `json:"cors_headers,omitempty"`
CustomDenyMessage string `json:"custom_deny_message,omitempty"`
CustomDenyURL string `json:"custom_deny_url,omitempty"`
CustomNonIdentityDenyURL string `json:"custom_non_identity_deny_url,omitempty"`
Domain string `json:"domain"`
EnableBindingCookie *bool `json:"enable_binding_cookie,omitempty"`
GatewayRules []AccessApplicationGatewayRule `json:"gateway_rules,omitempty"`
HttpOnlyCookieAttribute *bool `json:"http_only_cookie_attribute,omitempty"`
LogoURL string `json:"logo_url,omitempty"`
Name string `json:"name"`
PathCookieAttribute *bool `json:"path_cookie_attribute,omitempty"`
PrivateAddress string `json:"private_address"`
SaasApplication *SaasApplication `json:"saas_app,omitempty"`
SameSiteCookieAttribute string `json:"same_site_cookie_attribute,omitempty"`
SelfHostedDomains []string `json:"self_hosted_domains"`
ServiceAuth401Redirect *bool `json:"service_auth_401_redirect,omitempty"`
SessionDuration string `json:"session_duration,omitempty"`
SkipInterstitial *bool `json:"skip_interstitial,omitempty"`
Type AccessApplicationType `json:"type,omitempty"`
AllowAuthenticateViaWarp *bool `json:"allow_authenticate_via_warp,omitempty"`
OptionsPreflightBypass *bool `json:"options_preflight_bypass,omitempty"`
CustomPages []string `json:"custom_pages,omitempty"`
Tags []string `json:"tags,omitempty"`
SCIMConfig *AccessApplicationSCIMConfig `json:"scim_config,omitempty"`
// List of policy ids to link to this application in ascending order of precedence.
// Can reference reusable policies and policies specific to this application.
// If this field is not provided, the existing policies will not be modified.
Policies *[]string `json:"policies,omitempty"`
AccessAppLauncherCustomization
}
// ListAccessApplications returns all applications within an account or zone.
//
// Account API reference: https://developers.cloudflare.com/api/operations/access-applications-list-access-applications
// Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-applications-list-access-applications
func (api *API) ListAccessApplications(ctx context.Context, rc *ResourceContainer, params ListAccessApplicationsParams) ([]AccessApplication, *ResultInfo, error) {
baseURL := fmt.Sprintf("/%s/%s/access/apps", rc.Level, rc.Identifier)
autoPaginate := true
if params.PerPage >= 1 || params.Page >= 1 {
autoPaginate = false
}
if params.PerPage < 1 {
params.PerPage = 25
}
if params.Page < 1 {
params.Page = 1
}
var applications []AccessApplication
var r AccessApplicationListResponse
for {
uri := buildURI(baseURL, params)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return []AccessApplication{}, &ResultInfo{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
}
err = json.Unmarshal(res, &r)
if err != nil {
return []AccessApplication{}, &ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
applications = append(applications, r.Result...)
params.ResultInfo = r.ResultInfo.Next()
if params.ResultInfo.Done() || !autoPaginate {
break
}
}
return applications, &r.ResultInfo, nil
}
// GetAccessApplication returns a single application based on the application
// ID for either account or zone.
//
// Account API reference: https://developers.cloudflare.com/api/operations/access-applications-get-an-access-application
// Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-applications-get-an-access-application
func (api *API) GetAccessApplication(ctx context.Context, rc *ResourceContainer, applicationID string) (AccessApplication, error) {
uri := fmt.Sprintf(
"/%s/%s/access/apps/%s",
rc.Level,
rc.Identifier,
applicationID,
)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return AccessApplication{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
}
var accessApplicationDetailResponse AccessApplicationDetailResponse
err = json.Unmarshal(res, &accessApplicationDetailResponse)
if err != nil {
return AccessApplication{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return accessApplicationDetailResponse.Result, nil
}
// CreateAccessApplication creates a new access application.
//
// Account API reference: https://developers.cloudflare.com/api/operations/access-applications-add-an-application
// Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-applications-add-a-bookmark-application
func (api *API) CreateAccessApplication(ctx context.Context, rc *ResourceContainer, params CreateAccessApplicationParams) (AccessApplication, error) {
uri := fmt.Sprintf("/%s/%s/access/apps", rc.Level, rc.Identifier)
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
if err != nil {
return AccessApplication{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
}
var accessApplicationDetailResponse AccessApplicationDetailResponse
err = json.Unmarshal(res, &accessApplicationDetailResponse)
if err != nil {
return AccessApplication{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return accessApplicationDetailResponse.Result, nil
}
// UpdateAccessApplication updates an existing access application.
//
// Account API reference: https://developers.cloudflare.com/api/operations/access-applications-update-a-bookmark-application
// Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-applications-update-a-bookmark-application
func (api *API) UpdateAccessApplication(ctx context.Context, rc *ResourceContainer, params UpdateAccessApplicationParams) (AccessApplication, error) {
if params.ID == "" {
return AccessApplication{}, fmt.Errorf("access application ID cannot be empty")
}
uri := fmt.Sprintf(
"/%s/%s/access/apps/%s",
rc.Level,
rc.Identifier,
params.ID,
)
res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
if err != nil {
return AccessApplication{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
}
var accessApplicationDetailResponse AccessApplicationDetailResponse
err = json.Unmarshal(res, &accessApplicationDetailResponse)
if err != nil {
return AccessApplication{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return accessApplicationDetailResponse.Result, nil
}
// DeleteAccessApplication deletes an access application.
//
// Account API reference: https://developers.cloudflare.com/api/operations/access-applications-delete-an-access-application
// Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-applications-delete-an-access-application
func (api *API) DeleteAccessApplication(ctx context.Context, rc *ResourceContainer, applicationID string) error {
uri := fmt.Sprintf(
"/%s/%s/access/apps/%s",
rc.Level,
rc.Identifier,
applicationID,
)
_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return fmt.Errorf("%s: %w", errMakeRequestError, err)
}
return nil
}
// RevokeAccessApplicationTokens revokes tokens associated with an
// access application.
//
// Account API reference: https://developers.cloudflare.com/api/operations/access-applications-revoke-service-tokens
// Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-applications-revoke-service-tokens
func (api *API) RevokeAccessApplicationTokens(ctx context.Context, rc *ResourceContainer, applicationID string) error {
uri := fmt.Sprintf(
"/%s/%s/access/apps/%s/revoke-tokens",
rc.Level,
rc.Identifier,
applicationID,
)
_, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
if err != nil {
return fmt.Errorf("%s: %w", errMakeRequestError, err)
}
return nil
}