Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions identifier-registration.yaml.in
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ clients:
# origins:
# - https://my-host:8509

# - id: playground-trusted.js
# name: Trusted OIDC Playground with External Login
# trusted: yes
# application_type: web
# redirect_uris:
# - https://my-host:8509/
# origins:
# - https://my-host:8509
# external_authorize_redirect_uris:
# # Default external login URI used for any scope.
# - https://my-external-login:8443/authorize
# # Scope-specific URI used when the given scope is requested.
# - MyApp.Special:https://my-external-login:8443/authorize-special

# - id: playground-trusted.js
# name: Trusted Insecure OIDC Playground
# trusted: yes
Expand Down
2 changes: 1 addition & 1 deletion identifier/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ type Consent struct {
}

// Scopes returns the associated consents approved scopes filtered by the
//provided requested scopes and the full unfiltered approved scopes table.
// provided requested scopes and the full unfiltered approved scopes table.
func (c *Consent) Scopes(requestedScopes map[string]bool) (map[string]bool, map[string]bool) {
scopes := make(map[string]bool)
if c.RawScope != "" {
Expand Down
62 changes: 61 additions & 1 deletion identity/clients/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"crypto/subtle"
"encoding/base64"
"fmt"
"net/url"
"strings"
"time"

"github.com/golang-jwt/jwt/v5"
Expand Down Expand Up @@ -52,7 +54,8 @@
TrustedScopes []string `yaml:"trusted_scopes" json:"-"`
Insecure bool `yaml:"insecure" json:"-"`

ImplicitScopes []string `yaml:"implicit_scopes" json:"-"`
ImplicitScopes []string `yaml:"implicit_scopes" json:"-"`
ExternalAuthorizeRedirectURIs []string `yaml:"external_authorize_redirect_uris,flow" json:"-"`

Dynamic bool `yaml:"-" json:"-"`
IDIssuedAt time.Time `yaml:"-" json:"-"`
Expand Down Expand Up @@ -81,6 +84,23 @@
// Validate validates the associated client registration data and returns error
// if the data is not valid.
func (cr *ClientRegistration) Validate() error {
for _, entry := range cr.ExternalAuthorizeRedirectURIs {
uri := entry
// Strip scope prefix if present using the colon heuristic.
if idx := strings.Index(entry, ":"); idx > 0 {
rest := entry[idx+1:]
if !strings.HasPrefix(rest, "//") {
uri = rest
}
}
parsed, err := url.Parse(uri)
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
return fmt.Errorf("invalid external_authorize_redirect_uri: %v", entry)
}
if parsed.Scheme != "https" {
return fmt.Errorf("external_authorize_redirect_uri must use https: %v", entry)
}
}
return nil
}

Expand Down Expand Up @@ -200,6 +220,46 @@
return nil
}

// GetExternalAuthorizeRedirectURI returns the external authorize redirect URI
// for the given scopes. A scope-specific match takes precedence over the
// default. Returns empty string if none is configured.
func (cr *ClientRegistration) GetExternalAuthorizeRedirectURI(scopes map[string]bool) string {
if len(cr.ExternalAuthorizeRedirectURIs) == 0 {
return ""
}

var defaultURI string
scopedURIs := make(map[string]string)
for _, entry := range cr.ExternalAuthorizeRedirectURIs {
if idx := strings.Index(entry, ":"); idx > 0 {
rest := entry[idx+1:]
// If the remainder starts with "//", this is a plain URL with
// no scope prefix (e.g. https://...).
if !strings.HasPrefix(rest, "//") {
scopedURIs[entry[:idx]] = rest
continue
}
}
if defaultURI == "" {
defaultURI = entry
}
}

// Try to find a scope-specific match.
if scopes != nil {

Check failure on line 249 in identity/clients/models.go

View workflow job for this annotation

GitHub Actions / build

S1031: unnecessary nil check around range (staticcheck)
for scope, ok := range scopes {
if !ok {
continue
}
if u, found := scopedURIs[scope]; found {
return u
}
}
}

return defaultURI
}

func (cr *ClientRegistration) makeSecret(secret []byte) (string, string, error) {
// Create random secret. HMAC the client name with it to get the subject.
if secret == nil {
Expand Down
15 changes: 8 additions & 7 deletions identity/clients/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,14 @@ func NewRegistry(ctx context.Context, trustedURI *url.URL, registrationConfFilep
validateErr := client.Validate()
registerErr := r.Register(client)
fields := logrus.Fields{
"client_id": client.ID,
"with_client_secret": client.Secret != "",
"trusted": client.Trusted,
"insecure": client.Insecure,
"application_type": client.ApplicationType,
"redirect_uris": client.RedirectURIs,
"origins": client.Origins,
"client_id": client.ID,
"with_client_secret": client.Secret != "",
"trusted": client.Trusted,
"insecure": client.Insecure,
"application_type": client.ApplicationType,
"redirect_uris": client.RedirectURIs,
"origins": client.Origins,
"external_authorize_redirect_uris": client.ExternalAuthorizeRedirectURIs,
}

if validateErr != nil {
Expand Down
14 changes: 13 additions & 1 deletion identity/managers/identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ func (im *IdentifierIdentityManager) RegisterManagers(mgrs *managers.Managers) e
return im.identifier.RegisterManagers(mgrs)
}

// getSignInFormURI returns the sign-in form URI for the given client and
// scopes. If the client has a configured external authorize redirect URI, it
// is returned. Otherwise the default sign-in form URI is used.
func (im *IdentifierIdentityManager) getSignInFormURI(clientID string, scopes map[string]bool) string {
if registration, ok := im.clients.Get(context.Background(), clientID); ok && registration != nil {
if uri := registration.GetExternalAuthorizeRedirectURI(scopes); uri != "" {
return uri
}
}
return im.signInFormURI
}

// Authenticate implements the identity.Manager interface.
func (im *IdentifierIdentityManager) Authenticate(ctx context.Context, rw http.ResponseWriter, req *http.Request, ar *payload.AuthenticationRequest, next identity.Manager) (identity.AuthRecord, error) {
var user *identifierUser
Expand Down Expand Up @@ -254,7 +266,7 @@ func (im *IdentifierIdentityManager) Authenticate(ctx context.Context, rw http.R
query.Set("claims_scope", strings.Join(claimsScopes, " "))
}
}
u, _ := url.Parse(im.signInFormURI)
u, _ := url.Parse(im.getSignInFormURI(ar.ClientID, ar.Scopes))
u.RawQuery = query.Encode()
utils.WriteRedirect(rw, http.StatusFound, u, nil, false)

Expand Down
Loading