feat(oidc): id token for device authorization (#7088)

* cleanup todo

* pass id token details to oidc

* feat(oidc): id token for device authorization

This changes updates to the newest oidc version,
so the Device Authorization grant can return ID tokens when
the scope `openid` is set.
There is also some refactoring done, so that the eventstore can be
queried directly when polling for state.
The projection is cleaned up to a minimum with only data required for the login UI.

* try to be explicit wit hthe timezone to fix github

* pin oidc v3.8.0

* remove TBD entry
This commit is contained in:
Tim Möhlmann
2023-12-20 14:21:08 +02:00
committed by GitHub
parent e15f6229cd
commit e22689c125
25 changed files with 629 additions and 621 deletions

View File

@@ -5,11 +5,11 @@ import (
"time"
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -78,47 +78,39 @@ func (o *OPStorage) StoreDeviceAuthorization(ctx context.Context, clientID, devi
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 zerrors.ThrowPermissionDeniedf(nil, "OIDC-et1Ae", "grant type %q not allowed for client", oidc.GrantTypeDeviceCode)
}
scopes, err = o.assertProjectRoleScopes(ctx, clientID, scopes)
if err != nil {
return zerrors.ThrowPreconditionFailed(err, "OIDC-She4t", "Errors.Internal")
}
aggrID, details, err := o.command.AddDeviceAuth(ctx, clientID, deviceCode, userCode, expires, scopes)
details, err := o.command.AddDeviceAuth(ctx, clientID, deviceCode, userCode, expires, scopes)
if err == nil {
logger.SetFields("aggregate_id", aggrID, "details", details).Debug(logMsg)
logger.SetFields("details", details).Debug(logMsg)
}
return err
}
func newDeviceAuthorizationState(d *domain.DeviceAuth) *op.DeviceAuthorizationState {
func newDeviceAuthorizationState(d *query.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(),
Subject: d.Subject,
AMR: AuthMethodTypesToAMR(d.UserAuthMethods),
AuthTime: d.AuthTime,
}
}
// GetDeviceAuthorizatonState retieves the current state of the Device Authorization process.
// GetDeviceAuthorizatonState retrieves 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)
logger := logging.WithFields("device_code", deviceCode)
ctx, span := tracing.NewSpan(ctx)
defer func() {
@@ -128,7 +120,7 @@ func (o *OPStorage) GetDeviceAuthorizatonState(ctx context.Context, clientID, de
span.EndWithError(err)
}()
deviceAuth, err := o.query.DeviceAuthByDeviceCode(ctx, clientID, deviceCode)
deviceAuth, err := o.query.DeviceAuthByDeviceCode(ctx, deviceCode)
if err != nil {
return nil, err
}
@@ -139,38 +131,12 @@ func (o *OPStorage) GetDeviceAuthorizatonState(ctx context.Context, clientID, de
// 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)
_, err = o.command.CancelDeviceAuth(ctx, deviceAuth.DeviceCode, 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.