2022-04-01 11:18:58 +00:00
|
|
|
package model
|
|
|
|
|
2022-04-07 05:33:53 +00:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/sha256"
|
2022-10-20 02:16:36 +00:00
|
|
|
"database/sql"
|
2022-04-07 05:33:53 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
2022-06-28 00:21:57 +00:00
|
|
|
"github.com/mohae/deepcopy"
|
2022-04-07 05:33:53 +00:00
|
|
|
"github.com/ory/fosite"
|
|
|
|
"github.com/ory/fosite/handler/openid"
|
|
|
|
|
|
|
|
"github.com/authelia/authelia/v4/internal/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewOAuth2ConsentSession creates a new OAuth2ConsentSession.
|
2022-07-26 05:43:39 +00:00
|
|
|
func NewOAuth2ConsentSession(subject uuid.UUID, r fosite.Requester) (consent *OAuth2ConsentSession, err error) {
|
|
|
|
valid := subject.ID() != 0
|
|
|
|
|
2022-04-07 05:33:53 +00:00
|
|
|
consent = &OAuth2ConsentSession{
|
|
|
|
ClientID: r.GetClient().GetID(),
|
2022-07-26 05:43:39 +00:00
|
|
|
Subject: uuid.NullUUID{UUID: subject, Valid: valid},
|
2022-04-07 05:33:53 +00:00
|
|
|
Form: r.GetRequestForm().Encode(),
|
|
|
|
RequestedAt: r.GetRequestedAt(),
|
|
|
|
RequestedScopes: StringSlicePipeDelimited(r.GetRequestedScopes()),
|
|
|
|
RequestedAudience: StringSlicePipeDelimited(r.GetRequestedAudience()),
|
|
|
|
GrantedScopes: StringSlicePipeDelimited(r.GetGrantedScopes()),
|
|
|
|
GrantedAudience: StringSlicePipeDelimited(r.GetGrantedAudience()),
|
|
|
|
}
|
|
|
|
|
|
|
|
if consent.ChallengeID, err = uuid.NewRandom(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return consent, nil
|
|
|
|
}
|
|
|
|
|
2023-03-06 03:58:50 +00:00
|
|
|
// NewOAuth2BlacklistedJTI creates a new OAuth2BlacklistedJTI.
|
|
|
|
func NewOAuth2BlacklistedJTI(jti string, exp time.Time) (jtiBlacklist OAuth2BlacklistedJTI) {
|
|
|
|
return OAuth2BlacklistedJTI{
|
|
|
|
Signature: fmt.Sprintf("%x", sha256.Sum256([]byte(jti))),
|
|
|
|
ExpiresAt: exp,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-07 05:33:53 +00:00
|
|
|
// NewOAuth2SessionFromRequest creates a new OAuth2Session from a signature and fosite.Requester.
|
|
|
|
func NewOAuth2SessionFromRequest(signature string, r fosite.Requester) (session *OAuth2Session, err error) {
|
|
|
|
var (
|
|
|
|
subject string
|
2022-06-17 12:25:14 +00:00
|
|
|
sessionOpenID *OpenIDSession
|
2022-06-28 00:21:57 +00:00
|
|
|
ok bool
|
2022-06-17 12:25:14 +00:00
|
|
|
sessionData []byte
|
2022-04-07 05:33:53 +00:00
|
|
|
)
|
|
|
|
|
2022-06-28 00:21:57 +00:00
|
|
|
sessionOpenID, ok = r.GetSession().(*OpenIDSession)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("can't convert type '%T' to an *OAuth2Session", r.GetSession())
|
2022-04-07 05:33:53 +00:00
|
|
|
}
|
|
|
|
|
2022-06-17 12:25:14 +00:00
|
|
|
subject = sessionOpenID.GetSubject()
|
2022-04-07 05:33:53 +00:00
|
|
|
|
2022-06-17 12:25:14 +00:00
|
|
|
if sessionData, err = json.Marshal(sessionOpenID); err != nil {
|
2022-04-07 05:33:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &OAuth2Session{
|
2022-06-17 12:25:14 +00:00
|
|
|
ChallengeID: sessionOpenID.ChallengeID,
|
2022-04-07 05:33:53 +00:00
|
|
|
RequestID: r.GetID(),
|
|
|
|
ClientID: r.GetClient().GetID(),
|
|
|
|
Signature: signature,
|
|
|
|
RequestedAt: r.GetRequestedAt(),
|
|
|
|
Subject: subject,
|
|
|
|
RequestedScopes: StringSlicePipeDelimited(r.GetRequestedScopes()),
|
|
|
|
GrantedScopes: StringSlicePipeDelimited(r.GetGrantedScopes()),
|
|
|
|
RequestedAudience: StringSlicePipeDelimited(r.GetRequestedAudience()),
|
|
|
|
GrantedAudience: StringSlicePipeDelimited(r.GetGrantedAudience()),
|
|
|
|
Active: true,
|
|
|
|
Revoked: false,
|
|
|
|
Form: r.GetRequestForm().Encode(),
|
2022-06-17 12:25:14 +00:00
|
|
|
Session: sessionData,
|
2022-04-07 05:33:53 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-03-06 03:58:50 +00:00
|
|
|
// NewOAuth2PARContext creates a new Pushed Authorization Request Context as a OAuth2PARContext.
|
|
|
|
func NewOAuth2PARContext(contextID string, r fosite.AuthorizeRequester) (context *OAuth2PARContext, err error) {
|
|
|
|
var (
|
|
|
|
s *OpenIDSession
|
|
|
|
ok bool
|
|
|
|
req *fosite.AuthorizeRequest
|
|
|
|
session []byte
|
|
|
|
)
|
|
|
|
|
|
|
|
if s, ok = r.GetSession().(*OpenIDSession); !ok {
|
|
|
|
return nil, fmt.Errorf("can't convert type '%T' to an *OAuth2Session", r.GetSession())
|
2022-04-07 05:33:53 +00:00
|
|
|
}
|
2023-03-06 03:58:50 +00:00
|
|
|
|
|
|
|
if session, err = json.Marshal(s); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var handled StringSlicePipeDelimited
|
|
|
|
|
|
|
|
if req, ok = r.(*fosite.AuthorizeRequest); ok {
|
|
|
|
handled = StringSlicePipeDelimited(req.HandledResponseTypes)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &OAuth2PARContext{
|
|
|
|
Signature: contextID,
|
|
|
|
RequestID: r.GetID(),
|
|
|
|
ClientID: r.GetClient().GetID(),
|
|
|
|
RequestedAt: r.GetRequestedAt(),
|
|
|
|
Scopes: StringSlicePipeDelimited(r.GetRequestedScopes()),
|
|
|
|
Audience: StringSlicePipeDelimited(r.GetRequestedAudience()),
|
|
|
|
HandledResponseTypes: handled,
|
|
|
|
ResponseMode: string(r.GetResponseMode()),
|
|
|
|
DefaultResponseMode: string(r.GetDefaultResponseMode()),
|
|
|
|
Revoked: false,
|
|
|
|
Form: r.GetRequestForm().Encode(),
|
|
|
|
Session: session,
|
|
|
|
}, nil
|
2022-04-07 05:33:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-20 02:16:36 +00:00
|
|
|
// OAuth2ConsentPreConfig stores information about an OAuth2.0 Pre-Configured Consent.
|
|
|
|
type OAuth2ConsentPreConfig struct {
|
|
|
|
ID int64 `db:"id"`
|
|
|
|
ClientID string `db:"client_id"`
|
|
|
|
Subject uuid.UUID `db:"subject"`
|
|
|
|
|
|
|
|
CreatedAt time.Time `db:"created_at"`
|
|
|
|
ExpiresAt sql.NullTime `db:"expires_at"`
|
|
|
|
|
|
|
|
Revoked bool `db:"revoked"`
|
|
|
|
|
|
|
|
Scopes StringSlicePipeDelimited `db:"scopes"`
|
|
|
|
Audience StringSlicePipeDelimited `db:"audience"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasExactGrants returns true if the granted audience and scopes of this consent pre-configuration matches exactly with
|
|
|
|
// another audience and set of scopes.
|
|
|
|
func (s *OAuth2ConsentPreConfig) HasExactGrants(scopes, audience []string) (has bool) {
|
|
|
|
return s.HasExactGrantedScopes(scopes) && s.HasExactGrantedAudience(audience)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasExactGrantedAudience returns true if the granted audience of this consent matches exactly with another audience.
|
|
|
|
func (s *OAuth2ConsentPreConfig) HasExactGrantedAudience(audience []string) (has bool) {
|
|
|
|
return !utils.IsStringSlicesDifferent(s.Audience, audience)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasExactGrantedScopes returns true if the granted scopes of this consent matches exactly with another set of scopes.
|
|
|
|
func (s *OAuth2ConsentPreConfig) HasExactGrantedScopes(scopes []string) (has bool) {
|
|
|
|
return !utils.IsStringSlicesDifferent(s.Scopes, scopes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanConsent returns true if this pre-configuration can still provide consent.
|
|
|
|
func (s *OAuth2ConsentPreConfig) CanConsent() bool {
|
|
|
|
return !s.Revoked && (!s.ExpiresAt.Valid || s.ExpiresAt.Time.After(time.Now()))
|
|
|
|
}
|
|
|
|
|
2022-04-07 05:33:53 +00:00
|
|
|
// OAuth2ConsentSession stores information about an OAuth2.0 Consent.
|
|
|
|
type OAuth2ConsentSession struct {
|
2022-07-26 05:43:39 +00:00
|
|
|
ID int `db:"id"`
|
|
|
|
ChallengeID uuid.UUID `db:"challenge_id"`
|
|
|
|
ClientID string `db:"client_id"`
|
|
|
|
Subject uuid.NullUUID `db:"subject"`
|
2022-04-07 05:33:53 +00:00
|
|
|
|
|
|
|
Authorized bool `db:"authorized"`
|
|
|
|
Granted bool `db:"granted"`
|
|
|
|
|
2022-10-20 02:16:36 +00:00
|
|
|
RequestedAt time.Time `db:"requested_at"`
|
|
|
|
RespondedAt sql.NullTime `db:"responded_at"`
|
2022-04-07 05:33:53 +00:00
|
|
|
|
|
|
|
Form string `db:"form_data"`
|
|
|
|
|
|
|
|
RequestedScopes StringSlicePipeDelimited `db:"requested_scopes"`
|
|
|
|
GrantedScopes StringSlicePipeDelimited `db:"granted_scopes"`
|
|
|
|
RequestedAudience StringSlicePipeDelimited `db:"requested_audience"`
|
|
|
|
GrantedAudience StringSlicePipeDelimited `db:"granted_audience"`
|
2022-10-20 02:16:36 +00:00
|
|
|
|
|
|
|
PreConfiguration sql.NullInt64
|
|
|
|
}
|
|
|
|
|
|
|
|
// Grant grants the requested scopes and audience.
|
|
|
|
func (s *OAuth2ConsentSession) Grant() {
|
|
|
|
s.GrantedScopes = s.RequestedScopes
|
|
|
|
s.GrantedAudience = s.RequestedAudience
|
|
|
|
|
|
|
|
if !utils.IsStringInSlice(s.ClientID, s.GrantedAudience) {
|
|
|
|
s.GrantedAudience = append(s.GrantedAudience, s.ClientID)
|
|
|
|
}
|
2022-04-07 05:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// HasExactGrants returns true if the granted audience and scopes of this consent matches exactly with another
|
|
|
|
// audience and set of scopes.
|
2022-10-20 02:16:36 +00:00
|
|
|
func (s *OAuth2ConsentSession) HasExactGrants(scopes, audience []string) (has bool) {
|
2022-04-07 05:33:53 +00:00
|
|
|
return s.HasExactGrantedScopes(scopes) && s.HasExactGrantedAudience(audience)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasExactGrantedAudience returns true if the granted audience of this consent matches exactly with another audience.
|
2022-10-20 02:16:36 +00:00
|
|
|
func (s *OAuth2ConsentSession) HasExactGrantedAudience(audience []string) (has bool) {
|
2022-04-07 05:33:53 +00:00
|
|
|
return !utils.IsStringSlicesDifferent(s.GrantedAudience, audience)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasExactGrantedScopes returns true if the granted scopes of this consent matches exactly with another set of scopes.
|
2022-10-20 02:16:36 +00:00
|
|
|
func (s *OAuth2ConsentSession) HasExactGrantedScopes(scopes []string) (has bool) {
|
2022-04-07 05:33:53 +00:00
|
|
|
return !utils.IsStringSlicesDifferent(s.GrantedScopes, scopes)
|
|
|
|
}
|
|
|
|
|
2022-10-20 02:16:36 +00:00
|
|
|
// Responded returns true if the user has responded to the consent session.
|
|
|
|
func (s *OAuth2ConsentSession) Responded() bool {
|
|
|
|
return s.RespondedAt.Valid
|
|
|
|
}
|
|
|
|
|
2022-04-07 05:33:53 +00:00
|
|
|
// IsAuthorized returns true if the user has responded to the consent session and it was authorized.
|
2022-10-20 02:16:36 +00:00
|
|
|
func (s *OAuth2ConsentSession) IsAuthorized() bool {
|
2022-04-07 05:33:53 +00:00
|
|
|
return s.Responded() && s.Authorized
|
|
|
|
}
|
|
|
|
|
2022-10-20 02:16:36 +00:00
|
|
|
// IsDenied returns true if the user has responded to the consent session and it was not authorized.
|
|
|
|
func (s *OAuth2ConsentSession) IsDenied() bool {
|
|
|
|
return s.Responded() && !s.Authorized
|
|
|
|
}
|
2022-04-07 05:33:53 +00:00
|
|
|
|
2022-10-20 02:16:36 +00:00
|
|
|
// CanGrant returns true if the session can still grant a token. This is NOT indicative of if there is a user response
|
|
|
|
// to this consent request or if the user rejected the consent request.
|
|
|
|
func (s *OAuth2ConsentSession) CanGrant() bool {
|
|
|
|
if !s.Subject.Valid || s.Granted {
|
2022-04-07 05:33:53 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetForm returns the form.
|
2022-10-20 02:16:36 +00:00
|
|
|
func (s *OAuth2ConsentSession) GetForm() (form url.Values, err error) {
|
2022-04-07 05:33:53 +00:00
|
|
|
return url.ParseQuery(s.Form)
|
|
|
|
}
|
|
|
|
|
|
|
|
// OAuth2BlacklistedJTI represents a blacklisted JTI used with OAuth2.0.
|
|
|
|
type OAuth2BlacklistedJTI struct {
|
|
|
|
ID int `db:"id"`
|
|
|
|
Signature string `db:"signature"`
|
|
|
|
ExpiresAt time.Time `db:"expires_at"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// OAuth2Session represents a OAuth2.0 session.
|
|
|
|
type OAuth2Session struct {
|
|
|
|
ID int `db:"id"`
|
|
|
|
ChallengeID uuid.UUID `db:"challenge_id"`
|
|
|
|
RequestID string `db:"request_id"`
|
|
|
|
ClientID string `db:"client_id"`
|
|
|
|
Signature string `db:"signature"`
|
|
|
|
RequestedAt time.Time `db:"requested_at"`
|
|
|
|
Subject string `db:"subject"`
|
|
|
|
RequestedScopes StringSlicePipeDelimited `db:"requested_scopes"`
|
|
|
|
GrantedScopes StringSlicePipeDelimited `db:"granted_scopes"`
|
|
|
|
RequestedAudience StringSlicePipeDelimited `db:"requested_audience"`
|
|
|
|
GrantedAudience StringSlicePipeDelimited `db:"granted_audience"`
|
|
|
|
Active bool `db:"active"`
|
|
|
|
Revoked bool `db:"revoked"`
|
|
|
|
Form string `db:"form_data"`
|
|
|
|
Session []byte `db:"session_data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetSubject implements an interface required for RFC7523.
|
|
|
|
func (s *OAuth2Session) SetSubject(subject string) {
|
|
|
|
s.Subject = subject
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToRequest converts an OAuth2Session into a fosite.Request given a fosite.Session and fosite.Storage.
|
2022-09-03 01:51:02 +00:00
|
|
|
func (s *OAuth2Session) ToRequest(ctx context.Context, session fosite.Session, store fosite.Storage) (request *fosite.Request, err error) {
|
2022-04-07 05:33:53 +00:00
|
|
|
sessionData := s.Session
|
|
|
|
|
|
|
|
if session != nil {
|
|
|
|
if err = json.Unmarshal(sessionData, session); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := store.GetClient(ctx, s.ClientID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
values, err := url.ParseQuery(s.Form)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &fosite.Request{
|
|
|
|
ID: s.RequestID,
|
|
|
|
RequestedAt: s.RequestedAt,
|
|
|
|
Client: client,
|
|
|
|
RequestedScope: fosite.Arguments(s.RequestedScopes),
|
|
|
|
GrantedScope: fosite.Arguments(s.GrantedScopes),
|
|
|
|
RequestedAudience: fosite.Arguments(s.RequestedAudience),
|
|
|
|
GrantedAudience: fosite.Arguments(s.GrantedAudience),
|
|
|
|
Form: values,
|
|
|
|
Session: session,
|
|
|
|
}, nil
|
2022-04-01 11:18:58 +00:00
|
|
|
}
|
2022-06-28 00:21:57 +00:00
|
|
|
|
2023-03-06 03:58:50 +00:00
|
|
|
// OAuth2PARContext holds relevant information about a Pushed Authorization Request in order to process the authorization.
|
|
|
|
type OAuth2PARContext struct {
|
|
|
|
ID int `db:"id"`
|
|
|
|
Signature string `db:"signature"`
|
|
|
|
RequestID string `db:"request_id"`
|
|
|
|
ClientID string `db:"client_id"`
|
|
|
|
RequestedAt time.Time `db:"requested_at"`
|
|
|
|
Scopes StringSlicePipeDelimited `db:"scopes"`
|
|
|
|
Audience StringSlicePipeDelimited `db:"audience"`
|
|
|
|
HandledResponseTypes StringSlicePipeDelimited `db:"handled_response_types"`
|
|
|
|
ResponseMode string `db:"response_mode"`
|
|
|
|
DefaultResponseMode string `db:"response_mode_default"`
|
|
|
|
Revoked bool `db:"revoked"`
|
|
|
|
Form string `db:"form_data"`
|
|
|
|
Session []byte `db:"session_data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (par *OAuth2PARContext) ToAuthorizeRequest(ctx context.Context, session fosite.Session, store fosite.Storage) (request *fosite.AuthorizeRequest, err error) {
|
|
|
|
if session != nil {
|
|
|
|
if err = json.Unmarshal(par.Session, session); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
client fosite.Client
|
|
|
|
form url.Values
|
|
|
|
)
|
|
|
|
|
|
|
|
if client, err = store.GetClient(ctx, par.ClientID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if form, err = url.ParseQuery(par.Form); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
request = fosite.NewAuthorizeRequest()
|
|
|
|
|
|
|
|
request.Request = fosite.Request{
|
|
|
|
ID: par.RequestID,
|
|
|
|
RequestedAt: par.RequestedAt,
|
|
|
|
Client: client,
|
|
|
|
RequestedScope: fosite.Arguments(par.Scopes),
|
|
|
|
RequestedAudience: fosite.Arguments(par.Audience),
|
|
|
|
Form: form,
|
|
|
|
Session: session,
|
|
|
|
}
|
|
|
|
|
|
|
|
if par.ResponseMode != "" {
|
|
|
|
request.ResponseMode = fosite.ResponseModeType(par.ResponseMode)
|
|
|
|
}
|
|
|
|
|
|
|
|
if par.DefaultResponseMode != "" {
|
|
|
|
request.DefaultResponseMode = fosite.ResponseModeType(par.DefaultResponseMode)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(par.HandledResponseTypes) != 0 {
|
|
|
|
request.HandledResponseTypes = fosite.Arguments(par.HandledResponseTypes)
|
|
|
|
}
|
|
|
|
|
|
|
|
return request, nil
|
|
|
|
}
|
|
|
|
|
2022-06-28 00:21:57 +00:00
|
|
|
// OpenIDSession holds OIDC Session information.
|
|
|
|
type OpenIDSession struct {
|
|
|
|
*openid.DefaultSession `json:"id_token"`
|
|
|
|
|
|
|
|
ChallengeID uuid.UUID `db:"challenge_id"`
|
|
|
|
ClientID string
|
|
|
|
|
2022-10-05 05:05:23 +00:00
|
|
|
Extra map[string]any `json:"extra"`
|
2022-06-28 00:21:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clone copies the OpenIDSession to a new fosite.Session.
|
|
|
|
func (s *OpenIDSession) Clone() fosite.Session {
|
|
|
|
if s == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return deepcopy.Copy(s).(fosite.Session)
|
|
|
|
}
|