mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:17:35 +00:00
feat(api): allow Device Authorization Grant using custom login UI (#9387)
# Which Problems Are Solved The OAuth2 Device Authorization Grant could not yet been handled through the new login UI, resp. using the session API. This PR adds the ability for the login UI to get the required information to display the user and handle their decision (approve with authorization or deny) using the OIDC Service API. # How the Problems Are Solved - Added a `GetDeviceAuthorizationRequest` endpoint, which allows getting the `id`, `client_id`, `scope`, `app_name` and `project_name` of the device authorization request - Added a `AuthorizeOrDenyDeviceAuthorization` endpoint, which allows to approve/authorize with the session information or deny the request. The identification of the request is done by the `device_authorization_id` / `id` returned in the previous request. - To prevent leaking the `device_code` to the UI, but still having an easy reference, it's encrypted and returned as `id`, resp. decrypted when used. - Fixed returned error types for device token responses on token endpoint: - Explicitly return `access_denied` (without internal error) when user denied the request - Default to `invalid_grant` instead of `access_denied` - Explicitly check on initial state when approving the reqeust - Properly handle done case (also relates to initial check) - Documented the flow and handling in custom UIs (according to OIDC / SAML) # Additional Changes - fixed some typos and punctuation in the corresponding OIDC / SAML guides. - added some missing translations for auth and saml request # Additional Context - closes #6239 --------- Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
@@ -636,6 +637,230 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_GetDeviceAuthorizationRequest(t *testing.T) {
|
||||
project, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
client, err := Instance.CreateOIDCClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dep func() (*oidc.DeviceAuthorizationResponse, error)
|
||||
ctx context.Context
|
||||
want *oidc.DeviceAuthorizationResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Not found",
|
||||
dep: func() (*oidc.DeviceAuthorizationResponse, error) {
|
||||
return &oidc.DeviceAuthorizationResponse{
|
||||
UserCode: "notFound",
|
||||
}, nil
|
||||
},
|
||||
ctx: CTX,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
dep: func() (*oidc.DeviceAuthorizationResponse, error) {
|
||||
return Instance.CreateDeviceAuthorizationRequest(CTX, client.GetClientId(), "openid")
|
||||
},
|
||||
ctx: CTX,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
deviceAuth, err := tt.dep()
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.GetDeviceAuthorizationRequest(tt.ctx, &oidc_pb.GetDeviceAuthorizationRequestRequest{
|
||||
UserCode: deviceAuth.UserCode,
|
||||
})
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
authRequest := got.GetDeviceAuthorizationRequest()
|
||||
assert.NotNil(t, authRequest)
|
||||
assert.NotEmpty(t, authRequest.GetId())
|
||||
assert.Equal(t, client.GetClientId(), authRequest.GetClientId())
|
||||
assert.Contains(t, authRequest.GetScope(), "openid")
|
||||
assert.NotEmpty(t, authRequest.GetAppName())
|
||||
assert.NotEmpty(t, authRequest.GetProjectName())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_AuthorizeOrDenyDeviceAuthorization(t *testing.T) {
|
||||
project, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
client, err := Instance.CreateOIDCClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE)
|
||||
require.NoError(t, err)
|
||||
sessionResp := createSession(t, CTX, Instance.Users[integration.UserTypeOrgOwner].ID)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest
|
||||
AuthError string
|
||||
want *oidc_pb.AuthorizeOrDenyDeviceAuthorizationResponse
|
||||
wantURL *url.URL
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Not found",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: "123",
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionResp.GetSessionId(),
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "session not found",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: func() string {
|
||||
req, err := Instance.CreateDeviceAuthorizationRequest(CTX, client.GetClientId(), "openid")
|
||||
require.NoError(t, err)
|
||||
var id string
|
||||
assert.EventuallyWithT(t, func(collectT *assert.CollectT) {
|
||||
resp, err := Instance.Client.OIDCv2.GetDeviceAuthorizationRequest(CTX, &oidc_pb.GetDeviceAuthorizationRequestRequest{
|
||||
UserCode: req.UserCode,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
id = resp.GetDeviceAuthorizationRequest().GetId()
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
return id
|
||||
}(),
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: "foo",
|
||||
SessionToken: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "session token invalid",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: func() string {
|
||||
req, err := Instance.CreateDeviceAuthorizationRequest(CTX, client.GetClientId(), "openid")
|
||||
require.NoError(t, err)
|
||||
var id string
|
||||
assert.EventuallyWithT(t, func(collectT *assert.CollectT) {
|
||||
resp, err := Instance.Client.OIDCv2.GetDeviceAuthorizationRequest(CTX, &oidc_pb.GetDeviceAuthorizationRequestRequest{
|
||||
UserCode: req.UserCode,
|
||||
})
|
||||
assert.NoError(collectT, err)
|
||||
id = resp.GetDeviceAuthorizationRequest().GetId()
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
return id
|
||||
}(),
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionResp.GetSessionId(),
|
||||
SessionToken: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "deny device authorization",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: func() string {
|
||||
req, err := Instance.CreateDeviceAuthorizationRequest(CTX, client.GetClientId(), "openid")
|
||||
require.NoError(t, err)
|
||||
var id string
|
||||
assert.EventuallyWithT(t, func(collectT *assert.CollectT) {
|
||||
resp, err := Instance.Client.OIDCv2.GetDeviceAuthorizationRequest(CTX, &oidc_pb.GetDeviceAuthorizationRequestRequest{
|
||||
UserCode: req.UserCode,
|
||||
})
|
||||
assert.NoError(collectT, err)
|
||||
id = resp.GetDeviceAuthorizationRequest().GetId()
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
return id
|
||||
}(),
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Deny{},
|
||||
},
|
||||
want: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationResponse{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "authorize, no permission, error",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: func() string {
|
||||
req, err := Instance.CreateDeviceAuthorizationRequest(CTX, client.GetClientId(), "openid")
|
||||
require.NoError(t, err)
|
||||
var id string
|
||||
assert.EventuallyWithT(t, func(collectT *assert.CollectT) {
|
||||
resp, err := Instance.Client.OIDCv2.GetDeviceAuthorizationRequest(CTX, &oidc_pb.GetDeviceAuthorizationRequestRequest{
|
||||
UserCode: req.UserCode,
|
||||
})
|
||||
assert.NoError(collectT, err)
|
||||
id = resp.GetDeviceAuthorizationRequest().GetId()
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
return id
|
||||
}(),
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionResp.GetSessionId(),
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "authorize, with permission",
|
||||
ctx: CTXLoginClient,
|
||||
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: func() string {
|
||||
req, err := Instance.CreateDeviceAuthorizationRequest(CTX, client.GetClientId(), "openid")
|
||||
require.NoError(t, err)
|
||||
var id string
|
||||
assert.EventuallyWithT(t, func(collectT *assert.CollectT) {
|
||||
resp, err := Instance.Client.OIDCv2.GetDeviceAuthorizationRequest(CTX, &oidc_pb.GetDeviceAuthorizationRequestRequest{
|
||||
UserCode: req.UserCode,
|
||||
})
|
||||
assert.NoError(collectT, err)
|
||||
id = resp.GetDeviceAuthorizationRequest().GetId()
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
return id
|
||||
}(),
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionResp.GetSessionId(),
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := Client.AuthorizeOrDenyDeviceAuthorization(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createSession(t *testing.T, ctx context.Context, userID string) *session.CreateSessionResponse {
|
||||
sessionResp, err := Instance.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
|
@@ -2,6 +2,7 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
@@ -28,6 +29,54 @@ func (s *Server) GetAuthRequest(ctx context.Context, req *oidc_pb.GetAuthRequest
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateCallback(ctx context.Context, req *oidc_pb.CreateCallbackRequest) (*oidc_pb.CreateCallbackResponse, error) {
|
||||
switch v := req.GetCallbackKind().(type) {
|
||||
case *oidc_pb.CreateCallbackRequest_Error:
|
||||
return s.failAuthRequest(ctx, req.GetAuthRequestId(), v.Error)
|
||||
case *oidc_pb.CreateCallbackRequest_Session:
|
||||
return s.linkSessionToAuthRequest(ctx, req.GetAuthRequestId(), v.Session)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "OIDCv2-zee7A", "verification oneOf %T in method CreateCallback not implemented", v)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) GetDeviceAuthorizationRequest(ctx context.Context, req *oidc_pb.GetDeviceAuthorizationRequestRequest) (*oidc_pb.GetDeviceAuthorizationRequestResponse, error) {
|
||||
deviceRequest, err := s.query.DeviceAuthRequestByUserCode(ctx, req.GetUserCode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encrypted, err := s.encryption.Encrypt([]byte(deviceRequest.DeviceCode))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oidc_pb.GetDeviceAuthorizationRequestResponse{
|
||||
DeviceAuthorizationRequest: &oidc_pb.DeviceAuthorizationRequest{
|
||||
Id: base64.RawURLEncoding.EncodeToString(encrypted),
|
||||
ClientId: deviceRequest.ClientID,
|
||||
Scope: deviceRequest.Scopes,
|
||||
AppName: deviceRequest.AppName,
|
||||
ProjectName: deviceRequest.ProjectName,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AuthorizeOrDenyDeviceAuthorization(ctx context.Context, req *oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest) (*oidc_pb.AuthorizeOrDenyDeviceAuthorizationResponse, error) {
|
||||
deviceCode, err := s.deviceCodeFromID(req.GetDeviceAuthorizationId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch req.GetDecision().(type) {
|
||||
case *oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session:
|
||||
_, err = s.command.ApproveDeviceAuthWithSession(ctx, deviceCode, req.GetSession().GetSessionId(), req.GetSession().GetSessionToken())
|
||||
case *oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Deny:
|
||||
_, err = s.command.CancelDeviceAuth(ctx, deviceCode, domain.DeviceAuthCanceledDenied)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oidc_pb.AuthorizeOrDenyDeviceAuthorizationResponse{}, nil
|
||||
}
|
||||
|
||||
func authRequestToPb(a *query.AuthRequest) *oidc_pb.AuthRequest {
|
||||
pba := &oidc_pb.AuthRequest{
|
||||
Id: a.ID,
|
||||
@@ -87,17 +136,6 @@ func (s *Server) checkPermission(ctx context.Context, clientID string, userID st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateCallback(ctx context.Context, req *oidc_pb.CreateCallbackRequest) (*oidc_pb.CreateCallbackResponse, error) {
|
||||
switch v := req.GetCallbackKind().(type) {
|
||||
case *oidc_pb.CreateCallbackRequest_Error:
|
||||
return s.failAuthRequest(ctx, req.GetAuthRequestId(), v.Error)
|
||||
case *oidc_pb.CreateCallbackRequest_Session:
|
||||
return s.linkSessionToAuthRequest(ctx, req.GetAuthRequestId(), v.Session)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "OIDCv2-zee7A", "verification oneOf %T in method CreateCallback not implemented", v)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) failAuthRequest(ctx context.Context, authRequestID string, ae *oidc_pb.AuthorizationError) (*oidc_pb.CreateCallbackResponse, error) {
|
||||
details, aar, err := s.command.FailAuthRequest(ctx, authRequestID, errorReasonToDomain(ae.GetError()))
|
||||
if err != nil {
|
||||
@@ -215,3 +253,11 @@ func errorReasonToOIDC(reason oidc_pb.ErrorReason) string {
|
||||
return "server_error"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) deviceCodeFromID(deviceAuthID string) (string, error) {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(deviceAuthID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.encryption.DecryptString(decoded, s.encryption.EncryptionKeyID())
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||
)
|
||||
@@ -20,6 +21,7 @@ type Server struct {
|
||||
|
||||
op *oidc.Server
|
||||
externalSecure bool
|
||||
encryption crypto.EncryptionAlgorithm
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
@@ -29,12 +31,14 @@ func CreateServer(
|
||||
query *query.Queries,
|
||||
op *oidc.Server,
|
||||
externalSecure bool,
|
||||
encryption crypto.EncryptionAlgorithm,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
op: op,
|
||||
externalSecure: externalSecure,
|
||||
encryption: encryption,
|
||||
}
|
||||
}
|
||||
|
||||
|
127
internal/api/oidc/integration_test/token_device_test.go
Normal file
127
internal/api/oidc/integration_test/token_device_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
//go:build integration
|
||||
|
||||
package oidc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/app"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||
)
|
||||
|
||||
func TestServer_DeviceAuth(t *testing.T) {
|
||||
project, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
client, err := Instance.CreateOIDCClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scope []string
|
||||
decision func(t *testing.T, id string)
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "authorized",
|
||||
scope: []string{},
|
||||
decision: func(t *testing.T, id string) {
|
||||
sessionID, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||
_, err = Instance.Client.OIDCv2.AuthorizeOrDenyDeviceAuthorization(CTXLOGIN, &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: id,
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionID,
|
||||
SessionToken: sessionToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "authorized, with ZITADEL",
|
||||
scope: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, domain.ProjectScopeZITADEL},
|
||||
decision: func(t *testing.T, id string) {
|
||||
sessionID, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||
_, err = Instance.Client.OIDCv2.AuthorizeOrDenyDeviceAuthorization(CTXLOGIN, &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: id,
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionID,
|
||||
SessionToken: sessionToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "denied",
|
||||
scope: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, domain.ProjectScopeZITADEL},
|
||||
decision: func(t *testing.T, id string) {
|
||||
_, err = Instance.Client.OIDCv2.AuthorizeOrDenyDeviceAuthorization(CTXLOGIN, &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
|
||||
DeviceAuthorizationId: id,
|
||||
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Deny{
|
||||
Deny: &oidc_pb.Deny{},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
wantErr: oidc.ErrAccessDenied(),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
provider, err := rp.NewRelyingPartyOIDC(CTX, Instance.OIDCIssuer(), client.GetClientId(), "", "", tt.scope)
|
||||
require.NoError(t, err)
|
||||
deviceAuthorization, err := rp.DeviceAuthorization(CTX, tt.scope, provider, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
relyingPartyDone := make(chan struct{})
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(CTX, 1*time.Minute)
|
||||
defer func() {
|
||||
cancel()
|
||||
relyingPartyDone <- struct{}{}
|
||||
}()
|
||||
tokens, err := rp.DeviceAccessToken(ctx, deviceAuthorization.DeviceCode, time.Duration(deviceAuthorization.Interval)*time.Second, provider)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
|
||||
if tokens == nil {
|
||||
return
|
||||
}
|
||||
_, err = Instance.Client.Auth.GetMyUser(integration.WithAuthorizationToken(CTX, tokens.AccessToken), &auth.GetMyUserRequest{})
|
||||
if slices.Contains(tt.scope, domain.ProjectScopeZITADEL) {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
}()
|
||||
|
||||
var req *oidc_pb.GetDeviceAuthorizationRequestResponse
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
assert.EventuallyWithT(t, func(collectT *assert.CollectT) {
|
||||
req, err = Instance.Client.OIDCv2.GetDeviceAuthorizationRequest(CTX, &oidc_pb.GetDeviceAuthorizationRequestRequest{
|
||||
UserCode: deviceAuthorization.UserCode,
|
||||
})
|
||||
assert.NoError(collectT, err)
|
||||
}, retryDuration, tick)
|
||||
|
||||
tt.decision(t, req.GetDeviceAuthorizationRequest().GetId())
|
||||
|
||||
<-relyingPartyDone
|
||||
})
|
||||
}
|
||||
}
|
@@ -42,6 +42,9 @@ func (s *Server) DeviceToken(ctx context.Context, r *op.ClientRequest[oidc.Devic
|
||||
if state == domain.DeviceAuthStateExpired {
|
||||
return nil, oidc.ErrExpiredDeviceCode()
|
||||
}
|
||||
if state == domain.DeviceAuthStateDenied {
|
||||
return nil, oidc.ErrAccessDenied()
|
||||
}
|
||||
}
|
||||
return nil, oidc.ErrAccessDenied().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError)
|
||||
return nil, oidc.ErrInvalidGrant().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError)
|
||||
}
|
||||
|
@@ -59,6 +59,9 @@ func (c *Commands) ApproveDeviceAuth(
|
||||
if !model.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound")
|
||||
}
|
||||
if model.State != domain.DeviceAuthStateInitiated {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-GEJL3", "Errors.DeviceAuth.AlreadyHandled")
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(ctx, model.aggregate, userID, userOrgID, authMethods, authTime, preferredLanguage, userAgent, sessionID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -71,6 +74,60 @@ func (c *Commands) ApproveDeviceAuth(
|
||||
return writeModelToObjectDetails(&model.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ApproveDeviceAuthWithSession(
|
||||
ctx context.Context,
|
||||
deviceCode,
|
||||
sessionID,
|
||||
sessionToken string,
|
||||
) (*domain.ObjectDetails, error) {
|
||||
model, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, deviceCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !model.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-D2hf2", "Errors.DeviceAuth.NotFound")
|
||||
}
|
||||
if model.State != domain.DeviceAuthStateInitiated {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-D30Jf", "Errors.DeviceAuth.AlreadyHandled")
|
||||
}
|
||||
if err := c.checkPermission(ctx, domain.PermissionSessionLink, model.ResourceOwner, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID())
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = sessionWriteModel.CheckIsActive(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.sessionTokenVerifier(ctx, sessionToken, sessionWriteModel.AggregateID, sessionWriteModel.TokenID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(
|
||||
ctx,
|
||||
model.aggregate,
|
||||
sessionWriteModel.UserID,
|
||||
sessionWriteModel.UserResourceOwner,
|
||||
sessionWriteModel.AuthMethodTypes(),
|
||||
sessionWriteModel.AuthenticationTime(),
|
||||
sessionWriteModel.PreferredLanguage,
|
||||
sessionWriteModel.UserAgent,
|
||||
sessionID,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(model, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writeModelToObjectDetails(&model.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) CancelDeviceAuth(ctx context.Context, id string, reason domain.DeviceAuthCanceled) (*domain.ObjectDetails, error) {
|
||||
model, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, id)
|
||||
if err != nil {
|
||||
|
@@ -82,6 +82,7 @@ func (m *DeviceAuthWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
deviceauth.AddedEventType,
|
||||
deviceauth.ApprovedEventType,
|
||||
deviceauth.CanceledEventType,
|
||||
deviceauth.DoneEventType,
|
||||
).
|
||||
Builder()
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/id/mock"
|
||||
"github.com/zitadel/zitadel/internal/repository/deviceauth"
|
||||
"github.com/zitadel/zitadel/internal/repository/oidcsession"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@@ -265,6 +266,310 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_ApproveDeviceAuthFromSession(t *testing.T) {
|
||||
ctx := authz.WithInstanceID(context.Background(), "instance1")
|
||||
now := time.Now()
|
||||
pushErr := errors.New("pushErr")
|
||||
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
tokenVerifier func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error)
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
deviceCode string
|
||||
sessionID string
|
||||
sessionToken string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantDetails *domain.ObjectDetails
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "not found error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx,
|
||||
"notfound",
|
||||
"sessionID",
|
||||
"sessionToken",
|
||||
},
|
||||
wantErr: zerrors.ThrowNotFound(nil, "COMMAND-D2hf2", "Errors.DeviceAuth.NotFound"),
|
||||
},
|
||||
{
|
||||
name: "not initialized, error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
ctx,
|
||||
deviceauth.NewAggregate("deviceCode", "instance1"),
|
||||
"client_id", "deviceCode", "456", now,
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{"projectID", "clientID"}, true,
|
||||
),
|
||||
),
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewCanceledEvent(
|
||||
ctx,
|
||||
deviceauth.NewAggregate("deviceCode", "instance1"),
|
||||
domain.DeviceAuthCanceledDenied,
|
||||
)),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx,
|
||||
"deviceCode",
|
||||
"sessionID",
|
||||
"sessionToken",
|
||||
},
|
||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D30Jf", "Errors.DeviceAuth.AlreadyHandled"),
|
||||
},
|
||||
{
|
||||
name: "missing permission, error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
ctx,
|
||||
deviceauth.NewAggregate("deviceCode", "instance1"),
|
||||
"client_id", "deviceCode", "456", now,
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{"projectID", "clientID"}, true,
|
||||
),
|
||||
)),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx,
|
||||
"deviceCode",
|
||||
"sessionID",
|
||||
"sessionToken",
|
||||
},
|
||||
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||
},
|
||||
{
|
||||
name: "session not active, error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
ctx,
|
||||
deviceauth.NewAggregate("deviceCode", "instance1"),
|
||||
"client_id", "deviceCode", "456", now,
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{"projectID", "clientID"}, true,
|
||||
),
|
||||
)),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx,
|
||||
"deviceCode",
|
||||
"sessionID",
|
||||
"sessionToken",
|
||||
},
|
||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Flk38", "Errors.Session.NotExisting"),
|
||||
},
|
||||
{
|
||||
name: "invalid session token, error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
ctx,
|
||||
deviceauth.NewAggregate("deviceCode", "instance1"),
|
||||
"client_id", "deviceCode", "456", now,
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{"projectID", "clientID"}, true,
|
||||
),
|
||||
)),
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
session.NewAddedEvent(ctx,
|
||||
&session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
&domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
)),
|
||||
)),
|
||||
tokenVerifier: newMockTokenVerifierInvalid(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx,
|
||||
"deviceCode",
|
||||
"sessionID",
|
||||
"invalidToken",
|
||||
},
|
||||
wantErr: zerrors.ThrowPermissionDenied(nil, "COMMAND-sGr42", "Errors.Session.Token.Invalid"),
|
||||
},
|
||||
{
|
||||
name: "push error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
ctx,
|
||||
deviceauth.NewAggregate("deviceCode", "instance1"),
|
||||
"client_id", "deviceCode", "456", now,
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{"projectID", "clientID"}, true,
|
||||
),
|
||||
)),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
session.NewAddedEvent(ctx,
|
||||
&session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
&domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
session.NewUserCheckedEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
"userID", "orgID", testNow, &language.Afrikaans),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
session.NewPasswordCheckedEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
testNow),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
session.NewLifetimeSetEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
2*time.Minute),
|
||||
),
|
||||
),
|
||||
expectPushFailed(pushErr,
|
||||
deviceauth.NewApprovedEvent(
|
||||
ctx, deviceauth.NewAggregate("deviceCode", "instance1"), "userID", "orgID",
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
testNow, &language.Afrikaans, &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
tokenVerifier: newMockTokenVerifierValid(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx,
|
||||
"deviceCode",
|
||||
"sessionID",
|
||||
"sessionToken",
|
||||
},
|
||||
wantErr: pushErr,
|
||||
},
|
||||
{
|
||||
name: "authorized",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
ctx,
|
||||
deviceauth.NewAggregate("deviceCode", "instance1"),
|
||||
"client_id", "deviceCode", "456", now,
|
||||
[]string{"a", "b", "c"},
|
||||
[]string{"projectID", "clientID"}, true,
|
||||
),
|
||||
)),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
session.NewAddedEvent(ctx,
|
||||
&session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
&domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
session.NewUserCheckedEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
"userID", "orgID", testNow, &language.Afrikaans),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
session.NewPasswordCheckedEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
testNow),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
session.NewLifetimeSetEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
2*time.Minute),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
deviceauth.NewApprovedEvent(
|
||||
ctx, deviceauth.NewAggregate("deviceCode", "instance1"), "userID", "orgID",
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
testNow, &language.Afrikaans, &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
tokenVerifier: newMockTokenVerifierValid(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx,
|
||||
"deviceCode",
|
||||
"sessionID",
|
||||
"sessionToken",
|
||||
},
|
||||
wantDetails: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
sessionTokenVerifier: tt.fields.tokenVerifier,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
gotDetails, err := c.ApproveDeviceAuthWithSession(tt.args.ctx, tt.args.deviceCode, tt.args.sessionID, tt.args.sessionToken)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assertObjectDetails(t, tt.wantDetails, gotDetails)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_CancelDeviceAuth(t *testing.T) {
|
||||
ctx := authz.WithInstanceID(context.Background(), "instance1")
|
||||
now := time.Now()
|
||||
|
@@ -62,11 +62,13 @@ func (a *AuthRequestSAML) IsValid() bool {
|
||||
}
|
||||
|
||||
type AuthRequestDevice struct {
|
||||
ClientID string
|
||||
DeviceCode string
|
||||
UserCode string
|
||||
Scopes []string
|
||||
Audience []string
|
||||
ClientID string
|
||||
DeviceCode string
|
||||
UserCode string
|
||||
Scopes []string
|
||||
Audience []string
|
||||
AppName string
|
||||
ProjectName string
|
||||
}
|
||||
|
||||
func (*AuthRequestDevice) Type() AuthRequestType {
|
||||
|
@@ -144,7 +144,17 @@ func (m *MockRepository) ExpectPushFailed(err error, expectedCommands []eventsto
|
||||
assert.Equal(m.MockPusher.ctrl.T, expectedCommand.Creator(), commands[i].Creator())
|
||||
assert.Equal(m.MockPusher.ctrl.T, expectedCommand.Type(), commands[i].Type())
|
||||
assert.Equal(m.MockPusher.ctrl.T, expectedCommand.Revision(), commands[i].Revision())
|
||||
assert.Equal(m.MockPusher.ctrl.T, expectedCommand.Payload(), commands[i].Payload())
|
||||
var expectedPayload []byte
|
||||
expectedPayload, ok := expectedCommand.Payload().([]byte)
|
||||
if !ok {
|
||||
expectedPayload, _ = json.Marshal(expectedCommand.Payload())
|
||||
}
|
||||
if string(expectedPayload) == "" {
|
||||
expectedPayload = []byte("null")
|
||||
}
|
||||
gotPayload, _ := json.Marshal(commands[i].Payload())
|
||||
|
||||
assert.Equal(m.MockPusher.ctrl.T, expectedPayload, gotPayload)
|
||||
assert.ElementsMatch(m.MockPusher.ctrl.T, expectedCommand.UniqueConstraints(), commands[i].UniqueConstraints())
|
||||
}
|
||||
|
||||
|
@@ -466,3 +466,11 @@ func (i *Instance) CreateOIDCJWTProfileClient(ctx context.Context) (machine *man
|
||||
|
||||
return machine, name, keyResp.GetKeyDetails(), nil
|
||||
}
|
||||
|
||||
func (i *Instance) CreateDeviceAuthorizationRequest(ctx context.Context, clientID string, scopes ...string) (*oidc.DeviceAuthorizationResponse, error) {
|
||||
provider, err := i.CreateRelyingParty(ctx, clientID, "", scopes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rp.DeviceAuthorization(ctx, scopes, provider, nil)
|
||||
}
|
||||
|
@@ -86,15 +86,24 @@ var deviceAuthSelectColumns = []string{
|
||||
DeviceAuthRequestColumnUserCode.identifier(),
|
||||
DeviceAuthRequestColumnScopes.identifier(),
|
||||
DeviceAuthRequestColumnAudience.identifier(),
|
||||
AppColumnName.identifier(),
|
||||
ProjectColumnName.identifier(),
|
||||
}
|
||||
|
||||
func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*domain.AuthRequestDevice, error)) {
|
||||
return sq.Select(deviceAuthSelectColumns...).From(deviceAuthRequestTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||
return sq.Select(deviceAuthSelectColumns...).
|
||||
From(deviceAuthRequestTable.identifier()).
|
||||
LeftJoin(join(AppOIDCConfigColumnClientID, DeviceAuthRequestColumnClientID)).
|
||||
LeftJoin(join(AppColumnID, AppOIDCConfigColumnAppID)).
|
||||
LeftJoin(join(ProjectColumnID, AppColumnProjectID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*domain.AuthRequestDevice, error) {
|
||||
dst := new(domain.AuthRequestDevice)
|
||||
var (
|
||||
scopes database.TextArray[string]
|
||||
audience database.TextArray[string]
|
||||
scopes database.TextArray[string]
|
||||
audience database.TextArray[string]
|
||||
appName sql.NullString
|
||||
projectName sql.NullString
|
||||
)
|
||||
|
||||
err := row.Scan(
|
||||
@@ -103,15 +112,20 @@ func prepareDeviceAuthQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
|
||||
&dst.UserCode,
|
||||
&scopes,
|
||||
&audience,
|
||||
&appName,
|
||||
&projectName,
|
||||
)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-Sah9a", "Errors.DeviceAuth.NotExisting")
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-Sah9a", "Errors.DeviceAuth.NotFound")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Voo3o", "Errors.Internal")
|
||||
}
|
||||
dst.Scopes = scopes
|
||||
dst.Audience = audience
|
||||
dst.AppName = appName.String
|
||||
dst.ProjectName = projectName.String
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
}
|
||||
|
@@ -24,8 +24,17 @@ const (
|
||||
` projections.device_auth_requests2.device_code,` +
|
||||
` projections.device_auth_requests2.user_code,` +
|
||||
` projections.device_auth_requests2.scopes,` +
|
||||
` projections.device_auth_requests2.audience` +
|
||||
` FROM projections.device_auth_requests2`
|
||||
` projections.device_auth_requests2.audience,` +
|
||||
` projections.apps7.name,` +
|
||||
` projections.projects4.name` +
|
||||
` FROM projections.device_auth_requests2` +
|
||||
` LEFT JOIN projections.apps7_oidc_configs` +
|
||||
` ON projections.device_auth_requests2.client_id = projections.apps7_oidc_configs.client_id` +
|
||||
` AND projections.device_auth_requests2.instance_id = projections.apps7_oidc_configs.instance_id` +
|
||||
` LEFT JOIN projections.apps7 ON projections.apps7_oidc_configs.app_id = projections.apps7.id` +
|
||||
` AND projections.apps7_oidc_configs.instance_id = projections.apps7.instance_id` +
|
||||
` LEFT JOIN projections.projects4 ON projections.apps7.project_id = projections.projects4.id` +
|
||||
` AND projections.apps7.instance_id = projections.projects4.instance_id`
|
||||
expectedDeviceAuthWhereUserCodeQueryC = expectedDeviceAuthQueryC +
|
||||
` WHERE projections.device_auth_requests2.instance_id = $1` +
|
||||
` AND projections.device_auth_requests2.user_code = $2`
|
||||
@@ -40,13 +49,17 @@ var (
|
||||
"user-code",
|
||||
database.TextArray[string]{"a", "b", "c"},
|
||||
[]string{"projectID", "clientID"},
|
||||
"appName",
|
||||
"projectName",
|
||||
}
|
||||
expectedDeviceAuth = &domain.AuthRequestDevice{
|
||||
ClientID: "client-id",
|
||||
DeviceCode: "device1",
|
||||
UserCode: "user-code",
|
||||
Scopes: []string{"a", "b", "c"},
|
||||
Audience: []string{"projectID", "clientID"},
|
||||
ClientID: "client-id",
|
||||
DeviceCode: "device1",
|
||||
UserCode: "user-code",
|
||||
Scopes: []string{"a", "b", "c"},
|
||||
Audience: []string{"projectID", "clientID"},
|
||||
AppName: "appName",
|
||||
ProjectName: "projectName",
|
||||
}
|
||||
)
|
||||
|
||||
|
@@ -561,6 +561,7 @@ Errors:
|
||||
AlreadyExists: Auth Request вече съществува
|
||||
NotExisting: Auth Request не съществува
|
||||
WrongLoginClient: Auth Request, създаден от друг клиент за влизане
|
||||
AlreadyHandled: Заявката за удостоверяване вече е обработена
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Токенът за опресняване е невалиден
|
||||
Token:
|
||||
@@ -571,8 +572,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest вече съществува
|
||||
NotExisting: SAMLRequest не съществува
|
||||
WrongLoginClient: SAMLRequest, създаден от друг клиент за влизане
|
||||
AlreadyHandled: SAML заявката вече е обработена
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse не е издаден за този клиент
|
||||
DeviceAuth:
|
||||
NotFound: Заявката за авторизация на устройство не съществува
|
||||
AlreadyHandled: Заявката за авторизация на устройство вече е обработена
|
||||
Feature:
|
||||
NotExisting: Функцията не съществува
|
||||
TypeNotSupported: Типът функция не се поддържа
|
||||
|
@@ -541,6 +541,7 @@ Errors:
|
||||
AlreadyExists: Požadavek na autentizaci již existuje
|
||||
NotExisting: Požadavek na autentizaci neexistuje
|
||||
WrongLoginClient: Požadavek na autentizaci vytvořen jiným klientem přihlášení
|
||||
AlreadyHandled: Žádost o ověření již byla zpracována
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Obnovovací token je neplatný
|
||||
Token:
|
||||
@@ -551,8 +552,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest již existuje
|
||||
NotExisting: SAMLRequest neexistuje
|
||||
WrongLoginClient: SAMLRequest vytvořený jiným přihlašovacím klientem
|
||||
AlreadyHandled: SAML požadavek již byl zpracován
|
||||
SAMLSession:
|
||||
InvalidClient: Pro tohoto klienta nebyla vydána odpověď SAMLResponse
|
||||
DeviceAuth:
|
||||
NotFound: Žádost o autorizaci zařízení neexistuje
|
||||
AlreadyHandled: Žádost o autorizaci zařízení již byla zpracována
|
||||
Feature:
|
||||
NotExisting: Funkce neexistuje
|
||||
TypeNotSupported: Typ funkce není podporován
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Auth Request existiert bereits
|
||||
NotExisting: Auth Request existiert nicht
|
||||
WrongLoginClient: Auth Request wurde von einem anderen Login-Client erstellt
|
||||
AlreadyHandled: Auth Request wurde bereits bearbeitet
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Refresh Token ist ungültig
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest existiert bereits
|
||||
NotExisting: SAMLRequest existiert nicht
|
||||
WrongLoginClient: SAMLRequest wurde con einem andere Login-Client erstellt
|
||||
AlreadyHandled: SAMLRequest wurde bereits bearbeitet
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse wurde nicht für diesen Client ausgestellt
|
||||
DeviceAuth:
|
||||
NotFound: Die Geräteautorisierungsanforderung existiert nicht
|
||||
AlreadyHandled: Die Geräteautorisierungsanforderung wurde bereits bearbeitet
|
||||
Feature:
|
||||
NotExisting: Feature existiert nicht
|
||||
TypeNotSupported: Feature Typ wird nicht unterstützt
|
||||
|
@@ -544,6 +544,7 @@ Errors:
|
||||
AlreadyExists: Auth Request already exists
|
||||
NotExisting: Auth Request does not exist
|
||||
WrongLoginClient: Auth Request created by other login client
|
||||
AlreadyHandled: Auth Request has already been handled
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Refresh Token is invalid
|
||||
Token:
|
||||
@@ -554,8 +555,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest already exists
|
||||
NotExisting: SAMLRequest does not exist
|
||||
WrongLoginClient: SAMLRequest created by other login client
|
||||
AlreadyHandled: SAMLRequest has already been handled
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse was not issued for this client
|
||||
DeviceAuth:
|
||||
NotFound: Device Authorization Request does not exist
|
||||
AlreadyHandled: Device Authorization Request has already been handled
|
||||
Feature:
|
||||
NotExisting: Feature does not exist
|
||||
TypeNotSupported: Feature type is not supported
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Auth Request ya existe
|
||||
NotExisting: Auth Request no existe
|
||||
WrongLoginClient: Auth Request creado por otro cliente de inicio de sesión
|
||||
AlreadyHandled: Auth Request ya ha sido procesada
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: El token de refresco no es válido
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest ya existe
|
||||
NotExisting: SAMLRequest no existe
|
||||
WrongLoginClient: SAMLRequest creado por otro cliente de inicio de sesión
|
||||
AlreadyHandled: SAMLRequest ya ha sido procesada
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse no ha sido emitido para este cliente
|
||||
DeviceAuth:
|
||||
NotFound: La solicitud de autorización del dispositivo no existe
|
||||
AlreadyHandled: La solicitud de autorización del dispositivo ya ha sido procesada
|
||||
Feature:
|
||||
NotExisting: La característica no existe
|
||||
TypeNotSupported: El tipo de característica no es compatible
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Auth Request existe déjà
|
||||
NotExisting: Auth Request n'existe pas
|
||||
WrongLoginClient: Auth Request créé par un autre client de connexion
|
||||
AlreadyHandled: Auth Request a déjà été traitée
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Le jeton de rafraîchissement n'est pas valide
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest existe déjà
|
||||
NotExisting: SAMLRequest n'existe pas
|
||||
WrongLoginClient: SAMLRequest créé par un autre client de connexion
|
||||
AlreadyHandled: SAMLRequest a déjà été traitée
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse n'a pas été émise pour ce client
|
||||
DeviceAuth:
|
||||
NotFound: La demande d'autorisation de l'appareil n'existe pas
|
||||
AlreadyHandled: La demande d'autorisation de l'appareil a déjà été traitée
|
||||
Feature:
|
||||
NotExisting: La fonctionnalité n'existe pas
|
||||
TypeNotSupported: Le type de fonctionnalité n'est pas pris en charge
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Az Auth Request már létezik
|
||||
NotExisting: Az Auth Request nem létezik
|
||||
WrongLoginClient: Az Auth Requestet egy másik bejelentkezési kliens hozta létre
|
||||
AlreadyHandled: A hitelesítési kérelem már feldolgozva
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Az Refresh Token érvénytelen
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: A SAMLRequest már létezik
|
||||
NotExisting: A SAMLRequest nem létezik
|
||||
WrongLoginClient: A SAMLRequest egy másik bejelentkezési ügyfél által létrehozott
|
||||
AlreadyHandled: A SAMLRequest már feldolgozva
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse nem lett kiadva ehhez az ügyfélhez
|
||||
DeviceAuth:
|
||||
NotFound: Az eszközengedélyezési kérelem nem létezik
|
||||
AlreadyHandled: Az eszközengedélyezési kérelem már feldolgozva
|
||||
Feature:
|
||||
NotExisting: A funkció nem létezik
|
||||
TypeNotSupported: A funkció típusa nem támogatott
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Permintaan Otentikasi sudah ada
|
||||
NotExisting: Permintaan Otentikasi tidak ada
|
||||
WrongLoginClient: Permintaan Otentikasi dibuat oleh klien login lain
|
||||
AlreadyHandled: Permintaan Otentikasi sudah ditangani
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Token Penyegaran tidak valid
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest sudah ada
|
||||
NotExisting: SAMLRequest tidak ada
|
||||
WrongLoginClient: SAMLRequest dibuat oleh klien login lainnya
|
||||
AlreadyHandled: SAMLRequest sudah ditangani
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse tidak dikeluarkan untuk klien ini
|
||||
DeviceAuth:
|
||||
NotFound: Permintaan Otorisasi Perangkat tidak ada
|
||||
AlreadyHandled: Permintaan Otorisasi Perangkat sudah ditangani
|
||||
Feature:
|
||||
NotExisting: Fitur tidak ada
|
||||
TypeNotSupported: Jenis fitur tidak didukung
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Auth Request esiste già
|
||||
NotExisting: Auth Request non esiste
|
||||
WrongLoginClient: Auth Request creato da un altro client di accesso
|
||||
AlreadyHandled: Auth Request è già stata gestita
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Refresh Token non è valido
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest esiste già
|
||||
NotExisting: SAMLRequest non esiste
|
||||
WrongLoginClient: SAMLRequest creato da un altro client di accesso
|
||||
AlreadyHandled: SAMLRequest è già stata gestita
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse non è stato emesso per questo client
|
||||
DeviceAuth:
|
||||
NotFound: La richiesta di autorizzazione del dispositivo non esiste
|
||||
AlreadyHandled: La richiesta di autorizzazione del dispositivo è già stata gestita
|
||||
Feature:
|
||||
NotExisting: La funzionalità non esiste
|
||||
TypeNotSupported: Il tipo di funzionalità non è supportato
|
||||
|
@@ -544,6 +544,7 @@ Errors:
|
||||
AlreadyExists: AuthRequestはすでに存在する
|
||||
NotExisting: AuthRequest が存在しません
|
||||
WrongLoginClient: 他のログインクライアントによって作成された AuthRequest
|
||||
AlreadyHandled: 認証リクエストは既に処理済みです
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: 無効なリフレッシュトークンです
|
||||
Token:
|
||||
@@ -554,8 +555,12 @@ Errors:
|
||||
AlreadyExists: SAMLリクエストはすでに存在します
|
||||
NotExisting: SAMLリクエストが存在しません
|
||||
WrongLoginClient: 他のログイン クライアントによって作成された SAMLRequest
|
||||
AlreadyHandled: SAMLリクエストは既に処理済みです
|
||||
SAMLSession:
|
||||
InvalidClient: このクライアントに対してSAMLResponseは発行されませんでした
|
||||
DeviceAuth:
|
||||
NotFound: デバイス認証リクエストが存在しません
|
||||
AlreadyHandled: デバイス認証リクエストは既に処理済みです
|
||||
Feature:
|
||||
NotExisting: 機能が存在しません
|
||||
TypeNotSupported: 機能タイプはサポートされていません
|
||||
|
@@ -544,6 +544,7 @@ Errors:
|
||||
AlreadyExists: 인증 요청이 이미 존재합니다
|
||||
NotExisting: 인증 요청이 존재하지 않습니다
|
||||
WrongLoginClient: 다른 로그인 클라이언트에 의해 생성된 인증 요청
|
||||
AlreadyHandled: 인증 요청이 이미 처리되었습니다
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: 새로 고침 토큰이 유효하지 않습니다
|
||||
Token:
|
||||
@@ -554,8 +555,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest가 이미 존재합니다
|
||||
NotExisting: SAMLRequest가 존재하지 않습니다
|
||||
WrongLoginClient: 다른 로그인 클라이언트가 생성한 SAMLRequest
|
||||
AlreadyHandled: SAML 요청이 이미 처리되었습니다
|
||||
SAMLSession:
|
||||
InvalidClient: 이 클라이언트에 대해 SAMLResponse가 발행되지 않았습니다.
|
||||
DeviceAuth:
|
||||
NotFound: 장치 인증 요청이 존재하지 않습니다
|
||||
AlreadyHandled: 장치 인증 요청이 이미 처리되었습니다
|
||||
Feature:
|
||||
NotExisting: 기능이 존재하지 않습니다
|
||||
TypeNotSupported: 기능 유형이 지원되지 않습니다
|
||||
|
@@ -542,6 +542,7 @@ Errors:
|
||||
AlreadyExists: Барањето за автентикација веќе постои
|
||||
NotExisting: Барањето за автентикација не постои
|
||||
WrongLoginClient: Барањето за автификација беше креирано од друг клиент за најавување
|
||||
AlreadyHandled: Барањето за автентикација е веќе обработено
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Токенот за освежување е неважечки
|
||||
Token:
|
||||
@@ -552,8 +553,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest веќе постои
|
||||
NotExisting: SAMLRequest не постои
|
||||
WrongLoginClient: SAML Барање создадено од друг клиент за најавување
|
||||
AlreadyHandled: SAML барањето е веќе обработено
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse не беше издаден за овој клиент
|
||||
DeviceAuth:
|
||||
NotFound: Барањето за авторизација на уредот не постои
|
||||
AlreadyHandled: Барањето за авторизација на уредот е веќе обработено
|
||||
Feature:
|
||||
NotExisting: Функцијата не постои
|
||||
TypeNotSupported: Типот на функција не е поддржан
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Auth Verzoek bestaat al
|
||||
NotExisting: Auth Verzoek bestaat niet
|
||||
WrongLoginClient: Auth Verzoek aangemaakt door andere login client
|
||||
AlreadyHandled: Authenticatieverzoek is al verwerkt
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Refresh Token is ongeldig
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest bestaat al
|
||||
NotExisting: SAMLRequest bestaat niet
|
||||
WrongLoginClient: SAMLRequest aangemaakt door andere login client
|
||||
AlreadyHandled: SAML-verzoek is al verwerkt
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse is niet uitgegeven voor deze client
|
||||
DeviceAuth:
|
||||
NotFound: Apparaatautorisatieverzoek bestaat niet
|
||||
AlreadyHandled: Apparaatautorisatieverzoek is al verwerkt
|
||||
Feature:
|
||||
NotExisting: Functie bestaat niet
|
||||
TypeNotSupported: Functie type wordt niet ondersteund
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Auth Request już istnieje
|
||||
NotExisting: Auth Request nie istnieje
|
||||
WrongLoginClient: Auth Request utworzony przez innego klienta logowania
|
||||
AlreadyHandled: Żądanie uwierzytelnienia zostało już obsłużone
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Refresh Token jest nieprawidłowy
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest już istnieje
|
||||
NotExisting: SAMLRequest nie istnieje
|
||||
WrongLoginClient: SAMLRequest utworzony przez innego klienta logowania
|
||||
AlreadyHandled: Żądanie SAML zostało już obsłużone
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse nie został wydany dla tego klienta
|
||||
DeviceAuth:
|
||||
NotFound: Żądanie autoryzacji urządzenia nie istnieje
|
||||
AlreadyHandled: Żądanie autoryzacji urządzenia zostało już obsłużone
|
||||
Feature:
|
||||
NotExisting: Funkcja nie istnieje
|
||||
TypeNotSupported: Typ funkcji nie jest obsługiwany
|
||||
|
@@ -542,6 +542,7 @@ Errors:
|
||||
AlreadyExists: A solicitação de autenticação já existe
|
||||
NotExisting: A solicitação de autenticação não existe
|
||||
WrongLoginClient: A solicitação de autenticação foi criada por outro cliente de login
|
||||
AlreadyHandled: O pedido de autenticação já foi processado
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: O Refresh Token é inválido
|
||||
Token:
|
||||
@@ -552,8 +553,12 @@ Errors:
|
||||
AlreadyExists: O SAMLRequest já existe
|
||||
NotExisting: O SAMLRequest não existe
|
||||
WrongLoginClient: SAMLRequest criado por outro cliente de login
|
||||
AlreadyHandled: O pedido SAML já foi processado
|
||||
SAMLSession:
|
||||
InvalidClient: O SAMLResponse não foi emitido para este cliente
|
||||
DeviceAuth:
|
||||
NotFound: O pedido de autorização do dispositivo não existe
|
||||
AlreadyHandled: O pedido de autorização do dispositivo já foi processado
|
||||
Feature:
|
||||
NotExisting: O recurso não existe
|
||||
TypeNotSupported: O tipo de recurso não é compatível
|
||||
|
@@ -532,6 +532,7 @@ Errors:
|
||||
AlreadyExists: Запрос на аутентификацию уже существует
|
||||
NotExisting: Запрос на аутентификацию не существует
|
||||
WrongLoginClient: Запрос на аутентификацию, созданный другим клиентом входа
|
||||
AlreadyHandled: Запрос аутентификации уже обработан
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Маркер обновления недействителен
|
||||
Token:
|
||||
@@ -542,8 +543,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest уже существует
|
||||
NotExisting: SAMLRequest не существует
|
||||
WrongLoginClient: SAMLRequest создан другим клиентом входа
|
||||
AlreadyHandled: Запрос SAML уже обработан
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse не был отправлен для этого клиента
|
||||
DeviceAuth:
|
||||
NotFound: Запрос авторизации устройства не существует
|
||||
AlreadyHandled: Запрос авторизации устройства уже обработан
|
||||
Feature:
|
||||
NotExisting: ункция не существует
|
||||
TypeNotSupported: Тип объекта не поддерживается
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: Autentiseringsbegäran finns redan
|
||||
NotExisting: Autentiseringsbegäran existerar inte
|
||||
WrongLoginClient: Autentiseringsbegäran skapad av annan inloggningsklient
|
||||
AlreadyHandled: Autentiseringsbegäran har redan hanterats
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Uppdateringstoken är ogiltig
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest finns redan
|
||||
NotExisting: SAMLRequest finns inte
|
||||
WrongLoginClient: SAMLRequest skapad av annan inloggningsklient
|
||||
AlreadyHandled: SAML-begäran har redan hanterats
|
||||
SAMLSession:
|
||||
InvalidClient: SAMLResponse utfärdades inte för den här klienten
|
||||
DeviceAuth:
|
||||
NotFound: Begäran om enhetsauktorisering finns inte
|
||||
AlreadyHandled: Begäran om enhetsauktorisering har redan hanterats
|
||||
Feature:
|
||||
NotExisting: Funktionen existerar inte
|
||||
TypeNotSupported: Funktionstypen stöds inte
|
||||
|
@@ -543,6 +543,7 @@ Errors:
|
||||
AlreadyExists: AuthRequest已经存在
|
||||
NotExisting: AuthRequest不存在
|
||||
WrongLoginClient: 其他登录客户端创建的AuthRequest
|
||||
AlreadyHandled: 身份验证请求已被处理
|
||||
OIDCSession:
|
||||
RefreshTokenInvalid: Refresh Token 无效
|
||||
Token:
|
||||
@@ -553,8 +554,12 @@ Errors:
|
||||
AlreadyExists: SAMLRequest 已存在
|
||||
NotExisting: SAMLRequest不存在
|
||||
WrongLoginClient: 其他登录客户端创建的 SAMLRequest
|
||||
AlreadyHandled: SAML请求已被处理
|
||||
SAMLSession:
|
||||
InvalidClient: 未向该客户端发出 SAMLResponse
|
||||
DeviceAuth:
|
||||
NotFound: 设备授权请求不存在
|
||||
AlreadyHandled: 设备授权请求已被处理
|
||||
Feature:
|
||||
NotExisting: 功能不存在
|
||||
TypeNotSupported: 不支持功能类型
|
||||
|
Reference in New Issue
Block a user