ultisuite-backend/internal/authentik/sources.go
R3D347HR4Y d3c930cac6
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(identity-providers): add management for identity providers in admin API
- 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.
2026-06-09 09:36:38 +02:00

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
}