mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-24 08:41:33 +00:00
177 lines
5.6 KiB
Go
177 lines
5.6 KiB
Go
|
package oidc
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"time"
|
||
|
|
||
|
"github.com/zitadel/logging"
|
||
|
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||
|
"github.com/zitadel/oidc/v2/pkg/op"
|
||
|
|
||
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||
|
"github.com/zitadel/zitadel/internal/domain"
|
||
|
"github.com/zitadel/zitadel/internal/errors"
|
||
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
DeviceAuthDefaultLifetime = 5 * time.Minute
|
||
|
DeviceAuthDefaultPollInterval = 5 * time.Second
|
||
|
)
|
||
|
|
||
|
type DeviceAuthorizationConfig struct {
|
||
|
Lifetime time.Duration
|
||
|
PollInterval time.Duration
|
||
|
UserCode *UserCodeConfig
|
||
|
}
|
||
|
|
||
|
type UserCodeConfig struct {
|
||
|
CharSet string
|
||
|
CharAmount int
|
||
|
DashInterval int
|
||
|
}
|
||
|
|
||
|
// toOPConfig converts DeviceAuthorizationConfig to a [op.DeviceAuthorizationConfig],
|
||
|
// setting sane defaults for empty values.
|
||
|
// Safe to call when c is nil.
|
||
|
func (c *DeviceAuthorizationConfig) toOPConfig() op.DeviceAuthorizationConfig {
|
||
|
out := op.DeviceAuthorizationConfig{
|
||
|
Lifetime: DeviceAuthDefaultLifetime,
|
||
|
PollInterval: DeviceAuthDefaultPollInterval,
|
||
|
UserFormPath: login.EndpointDeviceAuth,
|
||
|
UserCode: op.UserCodeBase20,
|
||
|
}
|
||
|
if c == nil {
|
||
|
return out
|
||
|
}
|
||
|
if c.Lifetime != 0 {
|
||
|
out.Lifetime = c.Lifetime
|
||
|
}
|
||
|
if c.PollInterval != 0 {
|
||
|
out.PollInterval = c.PollInterval
|
||
|
}
|
||
|
|
||
|
if c.UserCode == nil {
|
||
|
return out
|
||
|
}
|
||
|
if c.UserCode.CharSet != "" {
|
||
|
out.UserCode.CharSet = c.UserCode.CharSet
|
||
|
}
|
||
|
if c.UserCode.CharAmount != 0 {
|
||
|
out.UserCode.CharAmount = c.UserCode.CharAmount
|
||
|
}
|
||
|
if c.UserCode.DashInterval != 0 {
|
||
|
out.UserCode.DashInterval = c.UserCode.CharAmount
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// StoreDeviceAuthorization creates a new Device Authorization request.
|
||
|
// Implements the op.DeviceAuthorizationStorage interface.
|
||
|
func (o *OPStorage) StoreDeviceAuthorization(ctx context.Context, clientID, deviceCode, userCode string, expires time.Time, scopes []string) (err error) {
|
||
|
const logMsg = "store device authorization"
|
||
|
logger := logging.WithFields("client_id", clientID, "device_code", deviceCode, "user_code", userCode, "expires", expires, "scopes", scopes)
|
||
|
|
||
|
ctx, span := tracing.NewSpan(ctx)
|
||
|
defer func() {
|
||
|
logger.OnError(err).Error(logMsg)
|
||
|
span.EndWithError(err)
|
||
|
}()
|
||
|
|
||
|
// TODO(muhlemmer): Remove the following code block with oidc v3
|
||
|
// https://github.com/zitadel/oidc/issues/370
|
||
|
client, err := o.GetClientByClientID(ctx, clientID)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if !op.ValidateGrantType(client, oidc.GrantTypeDeviceCode) {
|
||
|
return errors.ThrowPermissionDeniedf(nil, "OIDC-et1Ae", "grant type %q not allowed for client", oidc.GrantTypeDeviceCode)
|
||
|
}
|
||
|
|
||
|
scopes, err = o.assertProjectRoleScopes(ctx, clientID, scopes)
|
||
|
if err != nil {
|
||
|
return errors.ThrowPreconditionFailed(err, "OIDC-She4t", "Errors.Internal")
|
||
|
}
|
||
|
aggrID, details, err := o.command.AddDeviceAuth(ctx, clientID, deviceCode, userCode, expires, scopes)
|
||
|
if err == nil {
|
||
|
logger.SetFields("aggregate_id", aggrID, "details", details).Debug(logMsg)
|
||
|
}
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func newDeviceAuthorizationState(d *domain.DeviceAuth) *op.DeviceAuthorizationState {
|
||
|
return &op.DeviceAuthorizationState{
|
||
|
ClientID: d.ClientID,
|
||
|
Scopes: d.Scopes,
|
||
|
Expires: d.Expires,
|
||
|
Done: d.State.Done(),
|
||
|
Subject: d.Subject,
|
||
|
Denied: d.State.Denied(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetDeviceAuthorizatonState retieves the current state of the Device Authorization process.
|
||
|
// It implements the [op.DeviceAuthorizationStorage] interface and is used by devices that
|
||
|
// are polling until they successfully receive a token or we indicate a denied or expired state.
|
||
|
// As generated user codes are of low entropy, this implementation also takes care or
|
||
|
// device authorization request cleanup, when it has been Approved, Denied or Expired.
|
||
|
func (o *OPStorage) GetDeviceAuthorizatonState(ctx context.Context, clientID, deviceCode string) (state *op.DeviceAuthorizationState, err error) {
|
||
|
const logMsg = "get device authorization state"
|
||
|
logger := logging.WithFields("client_id", clientID, "device_code", deviceCode)
|
||
|
|
||
|
ctx, span := tracing.NewSpan(ctx)
|
||
|
defer func() {
|
||
|
if err != nil {
|
||
|
logger.WithError(err).Error(logMsg)
|
||
|
}
|
||
|
span.EndWithError(err)
|
||
|
}()
|
||
|
|
||
|
deviceAuth, err := o.query.DeviceAuthByDeviceCode(ctx, clientID, deviceCode)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
logger.SetFields(
|
||
|
"expires", deviceAuth.Expires, "scopes", deviceAuth.Scopes,
|
||
|
"subject", deviceAuth.Subject, "state", deviceAuth.State,
|
||
|
).Debug("device authorization state")
|
||
|
|
||
|
// Cancel the request if it is expired, only if it wasn't Done meanwhile
|
||
|
if !deviceAuth.State.Done() && deviceAuth.Expires.Before(time.Now()) {
|
||
|
_, err = o.command.CancelDeviceAuth(ctx, deviceAuth.AggregateID, domain.DeviceAuthCanceledExpired)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
deviceAuth.State = domain.DeviceAuthStateExpired
|
||
|
}
|
||
|
|
||
|
// When the request is more then initiated, it has been either Approved, Denied or Expired.
|
||
|
// At this point we should remove it from the DB to avoid user code conflicts.
|
||
|
if deviceAuth.State > domain.DeviceAuthStateInitiated {
|
||
|
_, err = o.command.RemoveDeviceAuth(ctx, deviceAuth.AggregateID)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return newDeviceAuthorizationState(deviceAuth), nil
|
||
|
}
|
||
|
|
||
|
// TODO(muhlemmer): remove the following methods with oidc v3.
|
||
|
// They are actually not used, but are required by the oidc device storage interface.
|
||
|
// https://github.com/zitadel/oidc/issues/371
|
||
|
func (o *OPStorage) GetDeviceAuthorizationByUserCode(ctx context.Context, userCode string) (*op.DeviceAuthorizationState, error) {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
func (o *OPStorage) CompleteDeviceAuthorization(ctx context.Context, userCode, subject string) (err error) {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (o *OPStorage) DenyDeviceAuthorization(ctx context.Context, userCode string) (err error) {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// TODO end.
|