diff --git a/README.md b/README.md index 8305ba0..8270e88 100644 --- a/README.md +++ b/README.md @@ -20,53 +20,7 @@ This library is a pre-release work in progress. It has not been thoroughly teste The endpoints for SSO SAML are not tested and `POST /sso/saml/acs` does not provide request and response types. If you need additional support for SSO SAML, please create an issue or a pull request. -Required for V1 release: -- Implement and test endpoints - - Client API - - [X] GET /health - - [X] GET /settings - - [X] GET /callback - - [X] POST /callback - - [X] GET /authorize - - [X] POST /invite - - [X] POST /signup - - [X] POST /recover - - [X] POST /magiclink - - [X] POST /otp - - [X] POST /token - - [X] GET /verify - - [X] POST /verify - - [X] POST /logout - - [X] GET /reauthenticate - - [X] GET /user - - [X] PUT /user - - [X] POST /factors - - [X] POST /factors/{factor_id}/verify - - [X] POST /factors/{factor_id}/challenge - - [X] DELETE /factors/{factor_id} - - [X] GET /sso/saml/metadata (not tested) - - [X] POST /sso/saml/acs (not tested) - - Admin API - - [X] GET /admin/audit - - [X] GET /admin/users - - [X] POST /admin/users - - [X] GET /admin/users/{user_id}/factors - - [X] DELETE /admin/users/{user_id}/factors/{factor_id} - - [X] PUT /admin/users/{user_id}/factors/{factor_id} - - [X] GET /admin/users/{user_id} - - [X] PUT /admin/users/{user_id} - - [X] DELETE /admin/users/{user_id} - - [X] POST /admin/generate_link - - [ ] GET /admin/sso/providers - - [ ] POST /admin/sso/providers - - [ ] GET /admin/sso/providers/{idp_id} - - [ ] PUT /admin/sso/providers/{idp_id} - - [ ] DELETE /admin/sso/providers/{idp_id} -- Test infrastructure - - [X] Postgres container with GoTrue config - - [X] GoTrue container - signup enabled, autoconfirm off - - [X] GoTrue container - signup enabled, autoconfirm on - - [X] GoTrue container - signup disabled +Still required for V1 release: - [ ] Support for Captcha tokens ## Quick start diff --git a/api.go b/api.go index e5fdd9c..49212a4 100644 --- a/api.go +++ b/api.go @@ -67,6 +67,27 @@ type Client interface { // Requires admin token. AdminGenerateLink(req types.AdminGenerateLinkRequest) (*types.AdminGenerateLinkResponse, error) + // GET /admin/sso/providers + // + // Get a list of all SAML SSO Identity Providers in the system. + AdminListSSOProviders() (*types.AdminListSSOProvidersResponse, error) + // POST /admin/sso/providers + // + // Create a new SAML SSO Identity Provider. + AdminCreateSSOProvider(req types.AdminCreateSSOProviderRequest) (*types.AdminCreateSSOProviderResponse, error) + // GET /admin/sso/providers/{idp_id} + // + // Get a SAML SSO Identity Provider by ID. + AdminGetSSOProvider(req types.AdminGetSSOProviderRequest) (*types.AdminGetSSOProviderResponse, error) + // PUT /admin/sso/providers/{idp_id} + // + // Update a SAML SSO Identity Provider by ID. + AdminUpdateSSOProvider(req types.AdminUpdateSSOProviderRequest) (*types.AdminUpdateSSOProviderResponse, error) + // DELETE /admin/sso/providers/{idp_id} + // + // Delete a SAML SSO Identity Provider by ID. + AdminDeleteSSOProvider(req types.AdminDeleteSSOProviderRequest) (*types.AdminDeleteSSOProviderResponse, error) + // POST /admin/users // // Creates the user based on the user_id specified. diff --git a/endpoints/adminssoproviders.go b/endpoints/adminssoproviders.go new file mode 100644 index 0000000..427c031 --- /dev/null +++ b/endpoints/adminssoproviders.go @@ -0,0 +1,184 @@ +package endpoints + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/kwoodhouse93/gotrue-go/types" +) + +const adminSSOPath = "/admin/sso/providers" + +// GET /admin/sso/providers +// +// Get a list of all SAML SSO Identity Providers in the system. +func (c *Client) AdminListSSOProviders() (*types.AdminListSSOProvidersResponse, error) { + r, err := c.newRequest(adminSSOPath, http.MethodGet, nil) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + fullBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("response status code %d", resp.StatusCode) + } + return nil, fmt.Errorf("response status code %d: %s", resp.StatusCode, fullBody) + } + + var res types.AdminListSSOProvidersResponse + err = json.NewDecoder(resp.Body).Decode(&res) + if err != nil { + return nil, err + } + + return &res, nil +} + +// POST /admin/sso/providers +// +// Create a new SAML SSO Identity Provider. +func (c *Client) AdminCreateSSOProvider(req types.AdminCreateSSOProviderRequest) (*types.AdminCreateSSOProviderResponse, error) { + body, err := json.Marshal(req) + if err != nil { + return nil, err + } + + r, err := c.newRequest(adminSSOPath, http.MethodPost, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + fullBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("response status code %d", resp.StatusCode) + } + return nil, fmt.Errorf("response status code %d: %s", resp.StatusCode, fullBody) + } + + var res types.AdminCreateSSOProviderResponse + err = json.NewDecoder(resp.Body).Decode(&res) + if err != nil { + return nil, err + } + + return &res, nil +} + +// GET /admin/sso/providers/{idp_id} +// +// Get a SAML SSO Identity Provider by ID. +func (c *Client) AdminGetSSOProvider(req types.AdminGetSSOProviderRequest) (*types.AdminGetSSOProviderResponse, error) { + r, err := c.newRequest(fmt.Sprintf("%s/%s", adminSSOPath, req.ProviderID), http.MethodGet, nil) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + fullBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("response status code %d", resp.StatusCode) + } + return nil, fmt.Errorf("response status code %d: %s", resp.StatusCode, fullBody) + } + + var res types.AdminGetSSOProviderResponse + err = json.NewDecoder(resp.Body).Decode(&res) + if err != nil { + return nil, err + } + + return &res, nil +} + +// PUT /admin/sso/providers/{idp_id} +// +// Update a SAML SSO Identity Provider by ID. +func (c *Client) AdminUpdateSSOProvider(req types.AdminUpdateSSOProviderRequest) (*types.AdminUpdateSSOProviderResponse, error) { + body, err := json.Marshal(req) + if err != nil { + return nil, err + } + + r, err := c.newRequest(fmt.Sprintf("%s/%s", adminSSOPath, req.ProviderID), http.MethodPut, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + fullBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("response status code %d", resp.StatusCode) + } + return nil, fmt.Errorf("response status code %d: %s", resp.StatusCode, fullBody) + } + + var res types.AdminUpdateSSOProviderResponse + err = json.NewDecoder(resp.Body).Decode(&res) + if err != nil { + return nil, err + } + + return &res, nil +} + +// DELETE /admin/sso/providers/{idp_id} +// +// Delete a SAML SSO Identity Provider by ID. +func (c *Client) AdminDeleteSSOProvider(req types.AdminDeleteSSOProviderRequest) (*types.AdminDeleteSSOProviderResponse, error) { + path := fmt.Sprintf("%s/%s", adminSSOPath, req.ProviderID) + r, err := c.newRequest(path, http.MethodDelete, nil) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + fullBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("response status code %d", resp.StatusCode) + } + return nil, fmt.Errorf("response status code %d: %s", resp.StatusCode, fullBody) + } + + var res types.AdminDeleteSSOProviderResponse + err = json.NewDecoder(resp.Body).Decode(&res) + if err != nil { + return nil, err + } + + return &res, nil +} diff --git a/types/api.go b/types/api.go index b3e2328..0f4f7ad 100644 --- a/types/api.go +++ b/types/api.go @@ -175,6 +175,85 @@ type AdminDeleteUserFactorRequest struct { FactorID uuid.UUID } +type SAMLAttribute struct { + Name string `json:"name,omitempty"` + Names []string `json:"names,omitempty"` + Default interface{} `json:"default,omitempty"` +} + +type SAMLAttributeMapping struct { + Keys map[string]SAMLAttribute `json:"keys,omitempty"` +} + +type SAMLProvider struct { + EntityID string `json:"entity_id"` + MetadataXML string `json:"metadata_xml,omitempty"` + MetadataURL *string `json:"metadata_url,omitempty"` + + AttributeMapping SAMLAttributeMapping `json:"attribute_mapping,omitempty"` +} + +type SSODomain struct { + Domain string `db:"domain" json:"domain"` +} + +type SSOProvider struct { + ID uuid.UUID `json:"id"` + ResourceID *string `json:"resource_id,omitempty"` + SAMLProvider SAMLProvider `json:"saml,omitempty"` + SSODomains []SSODomain `json:"domains"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type AdminListSSOProvidersResponse struct { + Providers []SSOProvider `json:"items"` +} + +type AdminCreateSSOProviderRequest struct { + ResourceID string `json:"resource_id"` + Type string `json:"type"` + MetadataURL string `json:"metadata_url"` + MetadataXML string `json:"metadata_xml"` + Domains []string `json:"domains"` + AttributeMapping SAMLAttributeMapping `json:"attribute_mapping"` +} + +type AdminCreateSSOProviderResponse struct { + SSOProvider +} + +type AdminGetSSOProviderRequest struct { + ProviderID uuid.UUID +} + +type AdminGetSSOProviderResponse struct { + SSOProvider +} + +type AdminUpdateSSOProviderRequest struct { + ProviderID uuid.UUID `json:"-"` + + ResourceID string `json:"resource_id"` + Type string `json:"type"` + MetadataURL string `json:"metadata_url"` + MetadataXML string `json:"metadata_xml"` + Domains []string `json:"domains"` + AttributeMapping SAMLAttributeMapping `json:"attribute_mapping"` +} + +type AdminUpdateSSOProviderResponse struct { + SSOProvider +} + +type AdminDeleteSSOProviderRequest struct { + ProviderID uuid.UUID +} + +type AdminDeleteSSOProviderResponse struct { + SSOProvider +} + type Provider string const (