- Introduced new endpoints for managing identity providers, including retrieval of redirect URIs and testing/syncing providers. - Enhanced organization settings to include identity provider configurations, allowing for self-enrollment and domain restrictions. - Implemented caching for access policies and added validation for identity provider secrets. - Added integration tests to ensure proper functionality of identity provider management and policy enforcement.
323 lines
9.7 KiB
Go
323 lines
9.7 KiB
Go
package authentik
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
type sourceRef struct {
|
|
PK string `json:"pk"`
|
|
Slug string `json:"slug"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type brandRef struct {
|
|
PK string `json:"pk"`
|
|
BrandUUID string `json:"brand_uuid"`
|
|
Domain string `json:"domain"`
|
|
FlowEnrollment *string `json:"flow_enrollment"`
|
|
}
|
|
|
|
type flowInstance struct {
|
|
PK string `json:"pk"`
|
|
Slug string `json:"slug"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (c *Client) FindOAuthSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) {
|
|
q := url.Values{}
|
|
q.Set("slug", slug)
|
|
var out listResponse[sourceRef]
|
|
if err := c.getJSON(ctx, "/api/v3/sources/oauth/?"+q.Encode(), &out); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(out.Results) == 0 {
|
|
return nil, false, nil
|
|
}
|
|
return &out.Results[0], true, nil
|
|
}
|
|
|
|
func (c *Client) FindSAMLSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) {
|
|
q := url.Values{}
|
|
q.Set("slug", slug)
|
|
var out listResponse[sourceRef]
|
|
if err := c.getJSON(ctx, "/api/v3/sources/saml/?"+q.Encode(), &out); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(out.Results) == 0 {
|
|
return nil, false, nil
|
|
}
|
|
return &out.Results[0], true, nil
|
|
}
|
|
|
|
func (c *Client) FindLDAPSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) {
|
|
q := url.Values{}
|
|
q.Set("slug", slug)
|
|
var out listResponse[sourceRef]
|
|
if err := c.getJSON(ctx, "/api/v3/sources/ldap/?"+q.Encode(), &out); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(out.Results) == 0 {
|
|
return nil, false, nil
|
|
}
|
|
return &out.Results[0], true, nil
|
|
}
|
|
|
|
type OAuthSourceRequest struct {
|
|
Name string
|
|
Slug string
|
|
Enabled bool
|
|
ProviderType string
|
|
ClientID string
|
|
ClientSecret string
|
|
AuthorizationURL string
|
|
AccessTokenURL string
|
|
ProfileURL string
|
|
Scopes string
|
|
AuthenticationFlow string
|
|
EnrollmentFlow string
|
|
}
|
|
|
|
func (c *Client) CreateOAuthSource(ctx context.Context, req OAuthSourceRequest) (string, error) {
|
|
body := map[string]any{
|
|
"name": req.Name,
|
|
"slug": req.Slug,
|
|
"enabled": req.Enabled,
|
|
"provider_type": req.ProviderType,
|
|
"consumer_key": req.ClientID,
|
|
"consumer_secret": req.ClientSecret,
|
|
"authorization_url": req.AuthorizationURL,
|
|
"access_token_url": req.AccessTokenURL,
|
|
"profile_url": req.ProfileURL,
|
|
"authentication_flow": req.AuthenticationFlow,
|
|
"enrollment_flow": req.EnrollmentFlow,
|
|
"user_matching_mode": "email_link",
|
|
"policy_engine_mode": "any",
|
|
"request_token_url": "",
|
|
"additional_scopes": req.Scopes,
|
|
}
|
|
var created sourceRef
|
|
if err := c.postJSON(ctx, "/api/v3/sources/oauth/", body, &created); err != nil {
|
|
return "", err
|
|
}
|
|
return created.PK, nil
|
|
}
|
|
|
|
func (c *Client) UpdateOAuthSource(ctx context.Context, pk string, req OAuthSourceRequest) error {
|
|
body := map[string]any{
|
|
"name": req.Name,
|
|
"slug": req.Slug,
|
|
"enabled": req.Enabled,
|
|
"provider_type": req.ProviderType,
|
|
"consumer_key": req.ClientID,
|
|
"authorization_url": req.AuthorizationURL,
|
|
"access_token_url": req.AccessTokenURL,
|
|
"profile_url": req.ProfileURL,
|
|
"authentication_flow": req.AuthenticationFlow,
|
|
"enrollment_flow": req.EnrollmentFlow,
|
|
"additional_scopes": req.Scopes,
|
|
}
|
|
if strings.TrimSpace(req.ClientSecret) != "" {
|
|
body["consumer_secret"] = req.ClientSecret
|
|
}
|
|
return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/oauth/%s/", pk), body)
|
|
}
|
|
|
|
func (c *Client) DeleteOAuthSource(ctx context.Context, pk string) error {
|
|
return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/oauth/%s/", pk))
|
|
}
|
|
|
|
type SAMLSourceRequest struct {
|
|
Name string
|
|
Slug string
|
|
Enabled bool
|
|
MetadataURL string
|
|
MetadataXML string
|
|
EntityID string
|
|
SSOURL string
|
|
SLOURL string
|
|
SigningCert string
|
|
AuthenticationFlow string
|
|
EnrollmentFlow string
|
|
}
|
|
|
|
func (c *Client) CreateSAMLSource(ctx context.Context, req SAMLSourceRequest) (string, error) {
|
|
body := map[string]any{
|
|
"name": req.Name,
|
|
"slug": req.Slug,
|
|
"enabled": req.Enabled,
|
|
"metadata_url": req.MetadataURL,
|
|
"sso_url": req.SSOURL,
|
|
"slo_url": req.SLOURL,
|
|
"issuer": req.EntityID,
|
|
"authentication_flow": req.AuthenticationFlow,
|
|
"enrollment_flow": req.EnrollmentFlow,
|
|
"user_matching_mode": "email_link",
|
|
"policy_engine_mode": "any",
|
|
}
|
|
if strings.TrimSpace(req.MetadataXML) != "" {
|
|
body["metadata"] = req.MetadataXML
|
|
}
|
|
if strings.TrimSpace(req.SigningCert) != "" {
|
|
body["verification_kp"] = req.SigningCert
|
|
}
|
|
var created sourceRef
|
|
if err := c.postJSON(ctx, "/api/v3/sources/saml/", body, &created); err != nil {
|
|
return "", err
|
|
}
|
|
return created.PK, nil
|
|
}
|
|
|
|
func (c *Client) UpdateSAMLSource(ctx context.Context, pk string, req SAMLSourceRequest) error {
|
|
body := map[string]any{
|
|
"name": req.Name,
|
|
"slug": req.Slug,
|
|
"enabled": req.Enabled,
|
|
"metadata_url": req.MetadataURL,
|
|
"sso_url": req.SSOURL,
|
|
"slo_url": req.SLOURL,
|
|
"issuer": req.EntityID,
|
|
"authentication_flow": req.AuthenticationFlow,
|
|
"enrollment_flow": req.EnrollmentFlow,
|
|
}
|
|
if strings.TrimSpace(req.MetadataXML) != "" {
|
|
body["metadata"] = req.MetadataXML
|
|
}
|
|
if strings.TrimSpace(req.SigningCert) != "" {
|
|
body["verification_kp"] = req.SigningCert
|
|
}
|
|
return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/saml/%s/", pk), body)
|
|
}
|
|
|
|
func (c *Client) DeleteSAMLSource(ctx context.Context, pk string) error {
|
|
return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/saml/%s/", pk))
|
|
}
|
|
|
|
type LDAPSourceRequest struct {
|
|
Name string
|
|
Slug string
|
|
Enabled bool
|
|
ServerURI string
|
|
BindDN string
|
|
BindPassword string
|
|
BaseDN string
|
|
UserFilter string
|
|
StartTLS bool
|
|
SyncUsers bool
|
|
AuthenticationFlow string
|
|
EnrollmentFlow string
|
|
}
|
|
|
|
func (c *Client) CreateLDAPSource(ctx context.Context, req LDAPSourceRequest) (string, error) {
|
|
body := map[string]any{
|
|
"name": req.Name,
|
|
"slug": req.Slug,
|
|
"enabled": req.Enabled,
|
|
"server_uri": req.ServerURI,
|
|
"bind_cn": req.BindDN,
|
|
"bind_password": req.BindPassword,
|
|
"base_dn": req.BaseDN,
|
|
"search_mode": "direct",
|
|
"start_tls": req.StartTLS,
|
|
"sync_users": req.SyncUsers,
|
|
"authentication_flow": req.AuthenticationFlow,
|
|
"enrollment_flow": req.EnrollmentFlow,
|
|
"user_matching_mode": "email_link",
|
|
"policy_engine_mode": "any",
|
|
}
|
|
if filter := strings.TrimSpace(req.UserFilter); filter != "" {
|
|
body["search_filter"] = filter
|
|
}
|
|
var created sourceRef
|
|
if err := c.postJSON(ctx, "/api/v3/sources/ldap/", body, &created); err != nil {
|
|
return "", err
|
|
}
|
|
return created.PK, nil
|
|
}
|
|
|
|
func (c *Client) UpdateLDAPSource(ctx context.Context, pk string, req LDAPSourceRequest) error {
|
|
body := map[string]any{
|
|
"name": req.Name,
|
|
"slug": req.Slug,
|
|
"enabled": req.Enabled,
|
|
"server_uri": req.ServerURI,
|
|
"bind_cn": req.BindDN,
|
|
"base_dn": req.BaseDN,
|
|
"start_tls": req.StartTLS,
|
|
"sync_users": req.SyncUsers,
|
|
"authentication_flow": req.AuthenticationFlow,
|
|
"enrollment_flow": req.EnrollmentFlow,
|
|
}
|
|
if filter := strings.TrimSpace(req.UserFilter); filter != "" {
|
|
body["search_filter"] = filter
|
|
}
|
|
if strings.TrimSpace(req.BindPassword) != "" {
|
|
body["bind_password"] = req.BindPassword
|
|
}
|
|
return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/ldap/%s/", pk), body)
|
|
}
|
|
|
|
func (c *Client) DeleteLDAPSource(ctx context.Context, pk string) error {
|
|
return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/ldap/%s/", pk))
|
|
}
|
|
|
|
func (c *Client) deleteJSON(ctx context.Context, path string) error {
|
|
return c.doJSON(ctx, "DELETE", path, nil, nil)
|
|
}
|
|
|
|
func (c *Client) FindDefaultBrand(ctx context.Context) (*brandRef, error) {
|
|
var out listResponse[brandRef]
|
|
if err := c.getJSON(ctx, "/api/v3/core/brands/", &out); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, b := range out.Results {
|
|
if b.Domain == "authentik-default" || strings.Contains(strings.ToLower(b.Domain), "default") {
|
|
return &b, nil
|
|
}
|
|
}
|
|
if len(out.Results) > 0 {
|
|
return &out.Results[0], nil
|
|
}
|
|
return nil, fmt.Errorf("no brand found")
|
|
}
|
|
|
|
func (c *Client) SetBrandEnrollmentFlow(ctx context.Context, brandPK string, flowPK *string) error {
|
|
body := map[string]any{}
|
|
if flowPK == nil || strings.TrimSpace(*flowPK) == "" {
|
|
body["flow_enrollment"] = nil
|
|
} else {
|
|
body["flow_enrollment"] = *flowPK
|
|
}
|
|
return c.patchJSON(ctx, fmt.Sprintf("/api/v3/core/brands/%s/", brandPK), body)
|
|
}
|
|
|
|
func (c *Client) FindFlowInstanceBySlug(ctx context.Context, slug string) (*flowInstance, bool, error) {
|
|
q := url.Values{}
|
|
q.Set("slug", slug)
|
|
var out listResponse[flowInstance]
|
|
if err := c.getJSON(ctx, "/api/v3/flows/instances/?"+q.Encode(), &out); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(out.Results) == 0 {
|
|
return nil, false, nil
|
|
}
|
|
return &out.Results[0], true, nil
|
|
}
|
|
|
|
func (c *Client) ResolveSourceFlows(ctx context.Context) (authFlow, enrollmentFlow string, err error) {
|
|
authFlow, err = c.FindFlowBySlug(ctx, "default-source-authentication")
|
|
if err != nil {
|
|
authFlow, err = c.FindFlowBySlug(ctx, "default-authentication-flow")
|
|
}
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
enrollmentFlow, err = c.FindFlowBySlug(ctx, "default-source-enrollment")
|
|
if err != nil {
|
|
enrollmentFlow, _ = c.FindFlowBySlug(ctx, "default-enrollment-flow")
|
|
}
|
|
return authFlow, enrollmentFlow, nil
|
|
}
|