mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:07:31 +00:00
Merge commit '416a35537f89b1c3ccd3d123289cea37b3309bba' into next-rc
This commit is contained in:
@@ -201,7 +201,7 @@ func (a *API) registerConnectServer(service server.ConnectServer) {
|
||||
methodNames[i] = string(methods.Get(i).Name())
|
||||
}
|
||||
a.connectServices[prefix] = methodNames
|
||||
a.RegisterHandlerPrefixes(handler, prefix)
|
||||
a.RegisterHandlerPrefixes(http_mw.CORSInterceptor(handler), prefix)
|
||||
}
|
||||
|
||||
// HandleFunc allows registering a [http.HandlerFunc] on an exact
|
||||
|
@@ -477,10 +477,10 @@ func waitForExecutionOnCondition(ctx context.Context, t *testing.T, instance *in
|
||||
if !assert.NoError(ttt, err) {
|
||||
return
|
||||
}
|
||||
if !assert.Len(ttt, got.GetResult(), 1) {
|
||||
if !assert.Len(ttt, got.GetExecutions(), 1) {
|
||||
return
|
||||
}
|
||||
gotTargets := got.GetResult()[0].GetTargets()
|
||||
gotTargets := got.GetExecutions()[0].GetTargets()
|
||||
// always first check length, otherwise its failed anyway
|
||||
if assert.Len(ttt, gotTargets, len(targets)) {
|
||||
for i := range targets {
|
||||
@@ -506,10 +506,10 @@ func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Inst
|
||||
if !assert.NoError(ttt, err) {
|
||||
return
|
||||
}
|
||||
if !assert.Len(ttt, got.GetResult(), 1) {
|
||||
if !assert.Len(ttt, got.GetTargets(), 1) {
|
||||
return
|
||||
}
|
||||
config := got.GetResult()[0]
|
||||
config := got.GetTargets()[0]
|
||||
assert.Equal(ttt, config.GetEndpoint(), endpoint)
|
||||
switch ty {
|
||||
case domain.TargetTypeWebhook:
|
||||
@@ -605,7 +605,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
|
||||
{Key: "added", Value: "value"},
|
||||
},
|
||||
}
|
||||
return expectPreUserinfoExecution(ctx, t, instance, req, response)
|
||||
return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response)
|
||||
},
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
@@ -630,7 +630,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
|
||||
"addedLog",
|
||||
},
|
||||
}
|
||||
return expectPreUserinfoExecution(ctx, t, instance, req, response)
|
||||
return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response)
|
||||
},
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
@@ -655,7 +655,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
|
||||
{Key: "key", Value: []byte("value")},
|
||||
},
|
||||
}
|
||||
return expectPreUserinfoExecution(ctx, t, instance, req, response)
|
||||
return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response)
|
||||
},
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
@@ -692,7 +692,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
|
||||
{Key: "added3", Value: "value3"},
|
||||
},
|
||||
}
|
||||
return expectPreUserinfoExecution(ctx, t, instance, req, response)
|
||||
return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response)
|
||||
},
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
@@ -755,7 +755,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *integration.Instance, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) {
|
||||
func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) {
|
||||
userEmail := gofakeit.Email()
|
||||
userPhone := "+41" + gofakeit.Phone()
|
||||
userResp := instance.CreateHumanUserVerified(ctx, instance.DefaultOrg.Id, userEmail, userPhone)
|
||||
@@ -767,7 +767,7 @@ func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *int
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
}
|
||||
expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", userResp, userEmail, userPhone)
|
||||
expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", clientID, userResp, userEmail, userPhone)
|
||||
|
||||
targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response)
|
||||
|
||||
@@ -845,7 +845,7 @@ func getAccessTokenClaims(ctx context.Context, t *testing.T, instance *integrati
|
||||
return claims
|
||||
}
|
||||
|
||||
func contextInfoForUserOIDC(instance *integration.Instance, function string, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfo {
|
||||
func contextInfoForUserOIDC(instance *integration.Instance, function string, clientID string, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfo {
|
||||
return &oidc_api.ContextInfo{
|
||||
Function: function,
|
||||
UserInfo: &oidc.UserInfo{
|
||||
@@ -878,6 +878,9 @@ func contextInfoForUserOIDC(instance *integration.Instance, function string, use
|
||||
},
|
||||
},
|
||||
UserMetadata: nil,
|
||||
Application: &oidc_api.ContextInfoApplication{
|
||||
ClientID: clientID,
|
||||
},
|
||||
Org: &query.UserInfoOrg{
|
||||
ID: instance.DefaultOrg.GetId(),
|
||||
Name: instance.DefaultOrg.GetName(),
|
||||
@@ -918,7 +921,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
|
||||
{Key: "added1", Value: "value"},
|
||||
},
|
||||
}
|
||||
return expectPreAccessTokenExecution(ctx, t, instance, req, response)
|
||||
return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response)
|
||||
},
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
@@ -943,7 +946,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
|
||||
"addedLog",
|
||||
},
|
||||
}
|
||||
return expectPreAccessTokenExecution(ctx, t, instance, req, response)
|
||||
return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response)
|
||||
},
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
@@ -968,7 +971,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
|
||||
{Key: "key", Value: []byte("value")},
|
||||
},
|
||||
}
|
||||
return expectPreAccessTokenExecution(ctx, t, instance, req, response)
|
||||
return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response)
|
||||
},
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
@@ -1005,7 +1008,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
|
||||
{Key: "added3", Value: "value3"},
|
||||
},
|
||||
}
|
||||
return expectPreAccessTokenExecution(ctx, t, instance, req, response)
|
||||
return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response)
|
||||
},
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
@@ -1060,7 +1063,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *integration.Instance, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) {
|
||||
func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) {
|
||||
userEmail := gofakeit.Email()
|
||||
userPhone := "+41" + gofakeit.Phone()
|
||||
userResp := instance.CreateHumanUserVerified(ctx, instance.DefaultOrg.Id, userEmail, userPhone)
|
||||
@@ -1072,7 +1075,7 @@ func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
}
|
||||
expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", userResp, userEmail, userPhone)
|
||||
expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", clientID, userResp, userEmail, userPhone)
|
||||
|
||||
targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response)
|
||||
|
||||
|
@@ -206,7 +206,7 @@ func TestServer_GetTarget(t *testing.T) {
|
||||
}
|
||||
assert.NoError(ttt, err)
|
||||
assert.EqualExportedValues(ttt, tt.want, got)
|
||||
}, retryDuration, tick, "timeout waiting for expected target result")
|
||||
}, retryDuration, tick, "timeout waiting for expected target Executions")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -253,7 +253,7 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Target{},
|
||||
Targets: []*action.Target{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -269,11 +269,11 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
response.Result[0].Id = resp.GetId()
|
||||
response.Result[0].Name = name
|
||||
response.Result[0].CreationDate = resp.GetCreationDate()
|
||||
response.Result[0].ChangeDate = resp.GetCreationDate()
|
||||
response.Result[0].SigningKey = resp.GetSigningKey()
|
||||
response.Targets[0].Id = resp.GetId()
|
||||
response.Targets[0].Name = name
|
||||
response.Targets[0].CreationDate = resp.GetCreationDate()
|
||||
response.Targets[0].ChangeDate = resp.GetCreationDate()
|
||||
response.Targets[0].SigningKey = resp.GetSigningKey()
|
||||
},
|
||||
req: &action.ListTargetsRequest{
|
||||
Filters: []*action.TargetSearchFilter{{}},
|
||||
@@ -284,7 +284,7 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Target{
|
||||
Targets: []*action.Target{
|
||||
{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
@@ -309,11 +309,11 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
response.Result[0].Id = resp.GetId()
|
||||
response.Result[0].Name = name
|
||||
response.Result[0].CreationDate = resp.GetCreationDate()
|
||||
response.Result[0].ChangeDate = resp.GetCreationDate()
|
||||
response.Result[0].SigningKey = resp.GetSigningKey()
|
||||
response.Targets[0].Id = resp.GetId()
|
||||
response.Targets[0].Name = name
|
||||
response.Targets[0].CreationDate = resp.GetCreationDate()
|
||||
response.Targets[0].ChangeDate = resp.GetCreationDate()
|
||||
response.Targets[0].SigningKey = resp.GetSigningKey()
|
||||
},
|
||||
req: &action.ListTargetsRequest{
|
||||
Filters: []*action.TargetSearchFilter{{}},
|
||||
@@ -324,7 +324,7 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Target{
|
||||
Targets: []*action.Target{
|
||||
{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
@@ -354,23 +354,23 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
response.Result[2].Id = resp1.GetId()
|
||||
response.Result[2].Name = name1
|
||||
response.Result[2].CreationDate = resp1.GetCreationDate()
|
||||
response.Result[2].ChangeDate = resp1.GetCreationDate()
|
||||
response.Result[2].SigningKey = resp1.GetSigningKey()
|
||||
response.Targets[2].Id = resp1.GetId()
|
||||
response.Targets[2].Name = name1
|
||||
response.Targets[2].CreationDate = resp1.GetCreationDate()
|
||||
response.Targets[2].ChangeDate = resp1.GetCreationDate()
|
||||
response.Targets[2].SigningKey = resp1.GetSigningKey()
|
||||
|
||||
response.Result[1].Id = resp2.GetId()
|
||||
response.Result[1].Name = name2
|
||||
response.Result[1].CreationDate = resp2.GetCreationDate()
|
||||
response.Result[1].ChangeDate = resp2.GetCreationDate()
|
||||
response.Result[1].SigningKey = resp2.GetSigningKey()
|
||||
response.Targets[1].Id = resp2.GetId()
|
||||
response.Targets[1].Name = name2
|
||||
response.Targets[1].CreationDate = resp2.GetCreationDate()
|
||||
response.Targets[1].ChangeDate = resp2.GetCreationDate()
|
||||
response.Targets[1].SigningKey = resp2.GetSigningKey()
|
||||
|
||||
response.Result[0].Id = resp3.GetId()
|
||||
response.Result[0].Name = name3
|
||||
response.Result[0].CreationDate = resp3.GetCreationDate()
|
||||
response.Result[0].ChangeDate = resp3.GetCreationDate()
|
||||
response.Result[0].SigningKey = resp3.GetSigningKey()
|
||||
response.Targets[0].Id = resp3.GetId()
|
||||
response.Targets[0].Name = name3
|
||||
response.Targets[0].CreationDate = resp3.GetCreationDate()
|
||||
response.Targets[0].ChangeDate = resp3.GetCreationDate()
|
||||
response.Targets[0].SigningKey = resp3.GetSigningKey()
|
||||
},
|
||||
req: &action.ListTargetsRequest{
|
||||
Filters: []*action.TargetSearchFilter{{}},
|
||||
@@ -381,7 +381,7 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
TotalResult: 3,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Target{
|
||||
Targets: []*action.Target{
|
||||
{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestAsync{
|
||||
@@ -427,13 +427,13 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
require.NoError(ttt, listErr)
|
||||
|
||||
// always first check length, otherwise its failed anyway
|
||||
if assert.Len(ttt, got.Result, len(tt.want.Result)) {
|
||||
for i := range tt.want.Result {
|
||||
assert.EqualExportedValues(ttt, tt.want.Result[i], got.Result[i])
|
||||
if assert.Len(ttt, got.Targets, len(tt.want.Targets)) {
|
||||
for i := range tt.want.Targets {
|
||||
assert.EqualExportedValues(ttt, tt.want.Targets[i], got.Targets[i])
|
||||
}
|
||||
}
|
||||
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
}, retryDuration, tick, "timeout waiting for expected execution Executions")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -476,9 +476,9 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
resp := instance.SetExecution(ctx, t, cond, []string{targetResp.GetId()})
|
||||
|
||||
// Set expected response with used values for SetExecution
|
||||
response.Result[0].CreationDate = resp.GetSetDate()
|
||||
response.Result[0].ChangeDate = resp.GetSetDate()
|
||||
response.Result[0].Condition = cond
|
||||
response.Executions[0].CreationDate = resp.GetSetDate()
|
||||
response.Executions[0].ChangeDate = resp.GetSetDate()
|
||||
response.Executions[0].Condition = cond
|
||||
},
|
||||
req: &action.ListExecutionsRequest{
|
||||
Filters: []*action.ExecutionSearchFilter{{
|
||||
@@ -503,7 +503,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Execution{
|
||||
Executions: []*action.Execution{
|
||||
{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -544,10 +544,10 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
}
|
||||
resp := instance.SetExecution(ctx, t, cond, []string{target.GetId()})
|
||||
|
||||
response.Result[0].CreationDate = resp.GetSetDate()
|
||||
response.Result[0].ChangeDate = resp.GetSetDate()
|
||||
response.Result[0].Condition = cond
|
||||
response.Result[0].Targets = []string{target.GetId()}
|
||||
response.Executions[0].CreationDate = resp.GetSetDate()
|
||||
response.Executions[0].ChangeDate = resp.GetSetDate()
|
||||
response.Executions[0].Condition = cond
|
||||
response.Executions[0].Targets = []string{target.GetId()}
|
||||
},
|
||||
req: &action.ListExecutionsRequest{
|
||||
Filters: []*action.ExecutionSearchFilter{{}},
|
||||
@@ -558,7 +558,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Execution{
|
||||
Executions: []*action.Execution{
|
||||
{
|
||||
Condition: &action.Condition{},
|
||||
Targets: []string{""},
|
||||
@@ -604,7 +604,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
|
||||
cond1 := request.Filters[0].GetInConditionsFilter().GetConditions()[0]
|
||||
resp1 := instance.SetExecution(ctx, t, cond1, []string{targetResp.GetId()})
|
||||
response.Result[2] = &action.Execution{
|
||||
response.Executions[2] = &action.Execution{
|
||||
CreationDate: resp1.GetSetDate(),
|
||||
ChangeDate: resp1.GetSetDate(),
|
||||
Condition: cond1,
|
||||
@@ -613,7 +613,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
|
||||
cond2 := request.Filters[0].GetInConditionsFilter().GetConditions()[1]
|
||||
resp2 := instance.SetExecution(ctx, t, cond2, []string{targetResp.GetId()})
|
||||
response.Result[1] = &action.Execution{
|
||||
response.Executions[1] = &action.Execution{
|
||||
CreationDate: resp2.GetSetDate(),
|
||||
ChangeDate: resp2.GetSetDate(),
|
||||
Condition: cond2,
|
||||
@@ -622,7 +622,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
|
||||
cond3 := request.Filters[0].GetInConditionsFilter().GetConditions()[2]
|
||||
resp3 := instance.SetExecution(ctx, t, cond3, []string{targetResp.GetId()})
|
||||
response.Result[0] = &action.Execution{
|
||||
response.Executions[0] = &action.Execution{
|
||||
CreationDate: resp3.GetSetDate(),
|
||||
ChangeDate: resp3.GetSetDate(),
|
||||
Condition: cond3,
|
||||
@@ -640,7 +640,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
TotalResult: 3,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Execution{
|
||||
Executions: []*action.Execution{
|
||||
{}, {}, {},
|
||||
},
|
||||
},
|
||||
@@ -653,7 +653,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
conditions := request.Filters[0].GetInConditionsFilter().GetConditions()
|
||||
for i, cond := range conditions {
|
||||
resp := instance.SetExecution(ctx, t, cond, []string{targetResp.GetId()})
|
||||
response.Result[(len(conditions)-1)-i] = &action.Execution{
|
||||
response.Executions[(len(conditions)-1)-i] = &action.Execution{
|
||||
CreationDate: resp.GetSetDate(),
|
||||
ChangeDate: resp.GetSetDate(),
|
||||
Condition: cond,
|
||||
@@ -687,7 +687,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
TotalResult: 10,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Execution{
|
||||
Executions: []*action.Execution{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
@@ -709,7 +709,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
conditions := request.Filters[0].GetInConditionsFilter().GetConditions()
|
||||
for i, cond := range conditions {
|
||||
resp := instance.SetExecution(ctx, t, cond, []string{targetResp.GetId()})
|
||||
response.Result[i] = &action.Execution{
|
||||
response.Executions[i] = &action.Execution{
|
||||
CreationDate: resp.GetSetDate(),
|
||||
ChangeDate: resp.GetSetDate(),
|
||||
Condition: cond,
|
||||
@@ -744,7 +744,7 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
TotalResult: 10,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.Execution{
|
||||
Executions: []*action.Execution{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
@@ -774,11 +774,11 @@ func TestServer_ListExecutions(t *testing.T) {
|
||||
}
|
||||
require.NoError(ttt, listErr)
|
||||
// always first check length, otherwise its failed anyway
|
||||
if assert.Len(ttt, got.Result, len(tt.want.Result)) {
|
||||
assert.EqualExportedValues(ttt, got.Result, tt.want.Result)
|
||||
if assert.Len(ttt, got.Executions, len(tt.want.Executions)) {
|
||||
assert.EqualExportedValues(ttt, got.Executions, tt.want.Executions)
|
||||
}
|
||||
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
}, retryDuration, tick, "timeout waiting for expected execution Executions")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ func (s *Server) ListTargets(ctx context.Context, req *connect.Request[action.Li
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&action.ListTargetsResponse{
|
||||
Result: targetsToPb(resp.Targets),
|
||||
Targets: targetsToPb(resp.Targets),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}), nil
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func (s *Server) ListExecutions(ctx context.Context, req *connect.Request[action
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&action.ListExecutionsResponse{
|
||||
Result: executionsToPb(resp.Executions),
|
||||
Executions: executionsToPb(resp.Executions),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}), nil
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ func (s *Server) ListInstanceDomains(ctx context.Context, req *admin_pb.ListInst
|
||||
}
|
||||
return &admin_pb.ListInstanceDomainsResponse{
|
||||
Result: instance_grpc.DomainsToPb(domains.Domains),
|
||||
SortingColumn: req.SortingColumn,
|
||||
Details: object.ToListDetails(
|
||||
domains.Count,
|
||||
domains.Sequence,
|
||||
@@ -49,6 +50,7 @@ func (s *Server) ListInstanceTrustedDomains(ctx context.Context, req *admin_pb.L
|
||||
}
|
||||
return &admin_pb.ListInstanceTrustedDomainsResponse{
|
||||
Result: instance_grpc.TrustedDomainsToPb(domains.Domains),
|
||||
SortingColumn: req.SortingColumn,
|
||||
Details: object.ToListDetails(
|
||||
domains.Count,
|
||||
domains.Sequence,
|
||||
|
@@ -51,8 +51,23 @@ func ListInstanceTrustedDomainsRequestToModel(req *admin_pb.ListInstanceTrustedD
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: fieldNameToInstanceDomainColumn(req.SortingColumn),
|
||||
SortingColumn: fieldNameToInstanceTrustedDomainColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func fieldNameToInstanceTrustedDomainColumn(fieldName instance.DomainFieldName) query.Column {
|
||||
switch fieldName {
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN:
|
||||
return query.InstanceTrustedDomainDomainCol
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE:
|
||||
return query.InstanceTrustedDomainCreationDateCol
|
||||
case instance.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED,
|
||||
instance.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY,
|
||||
instance.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED:
|
||||
return query.InstanceTrustedDomainCreationDateCol
|
||||
default:
|
||||
return query.Column{}
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,6 @@ import (
|
||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
)
|
||||
|
||||
@@ -104,17 +103,5 @@ func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (*
|
||||
}
|
||||
|
||||
func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain string) ([]string, error) {
|
||||
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+orgDomain, query.TextEndsWithIgnoreCase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs := make([]string, len(users.Users))
|
||||
for i, user := range users.Users {
|
||||
userIDs[i] = user.ID
|
||||
}
|
||||
return userIDs, nil
|
||||
return s.query.SearchClaimedUserIDsOfOrgDomain(ctx, orgDomain, "")
|
||||
}
|
||||
|
@@ -316,28 +316,7 @@ func (s *Server) RemoveOrgMember(ctx context.Context, req *mgmt_pb.RemoveOrgMemb
|
||||
}
|
||||
|
||||
func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain, orgID string) ([]string, error) {
|
||||
queries := make([]query.SearchQuery, 0, 2)
|
||||
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+orgDomain, query.TextEndsWithIgnoreCase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, loginName)
|
||||
if orgID != "" {
|
||||
owner, err := query.NewUserResourceOwnerSearchQuery(orgID, query.TextNotEquals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, owner)
|
||||
}
|
||||
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: queries}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs := make([]string, len(users.Users))
|
||||
for i, user := range users.Users {
|
||||
userIDs[i] = user.ID
|
||||
}
|
||||
return userIDs, nil
|
||||
return s.query.SearchClaimedUserIDsOfOrgDomain(ctx, orgDomain, orgID)
|
||||
}
|
||||
|
||||
func (s *Server) ListOrgMetadata(ctx context.Context, req *mgmt_pb.ListOrgMetadataRequest) (*mgmt_pb.ListOrgMetadataResponse, error) {
|
||||
|
@@ -47,14 +47,17 @@ func TestMain(m *testing.M) {
|
||||
func TestServer_CreateOrganization(t *testing.T) {
|
||||
idpResp := Instance.AddGenericOAuthProvider(CTX, Instance.DefaultOrg.Id)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *v2beta_org.CreateOrganizationRequest
|
||||
id string
|
||||
want *v2beta_org.CreateOrganizationResponse
|
||||
wantErr bool
|
||||
}{
|
||||
type test struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *v2beta_org.CreateOrganizationRequest
|
||||
id string
|
||||
testFunc func(ctx context.Context, t *testing.T)
|
||||
want *v2beta_org.CreateOrganizationResponse
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
|
||||
@@ -73,6 +76,25 @@ func TestServer_CreateOrganization(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
func() test {
|
||||
orgName := gofakeit.Name()
|
||||
return test{
|
||||
name: "adding org with same name twice",
|
||||
ctx: CTX,
|
||||
req: &v2beta_org.CreateOrganizationRequest{
|
||||
Name: orgName,
|
||||
Admins: nil,
|
||||
},
|
||||
testFunc: func(ctx context.Context, t *testing.T) {
|
||||
// create org initially
|
||||
_, err := Client.CreateOrganization(ctx, &v2beta_org.CreateOrganizationRequest{
|
||||
Name: orgName,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
wantErr: true,
|
||||
}
|
||||
}(),
|
||||
{
|
||||
name: "invalid admin type",
|
||||
ctx: CTX,
|
||||
@@ -208,11 +230,38 @@ func TestServer_CreateOrganization(t *testing.T) {
|
||||
Name: gofakeit.AppName(),
|
||||
Id: gu.Ptr("custom_id"),
|
||||
},
|
||||
want: &v2beta_org.CreateOrganizationResponse{},
|
||||
want: &v2beta_org.CreateOrganizationResponse{
|
||||
Id: "custom_id",
|
||||
},
|
||||
},
|
||||
func() test {
|
||||
orgID := gofakeit.Name()
|
||||
return test{
|
||||
name: "adding org with same ID twice",
|
||||
ctx: CTX,
|
||||
req: &v2beta_org.CreateOrganizationRequest{
|
||||
Id: &orgID,
|
||||
Name: gofakeit.Name(),
|
||||
Admins: nil,
|
||||
},
|
||||
testFunc: func(ctx context.Context, t *testing.T) {
|
||||
// create org initially
|
||||
_, err := Client.CreateOrganization(ctx, &v2beta_org.CreateOrganizationRequest{
|
||||
Id: &orgID,
|
||||
Name: gofakeit.Name(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
wantErr: true,
|
||||
}
|
||||
}(),
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.testFunc != nil {
|
||||
tt.testFunc(tt.ctx, t)
|
||||
}
|
||||
|
||||
got, err := Client.CreateOrganization(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
@@ -1044,6 +1093,70 @@ func TestServer_AddOrganizationDomain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_AddOrganizationDomain_ClaimDomain(t *testing.T) {
|
||||
domain := gofakeit.DomainName()
|
||||
|
||||
// create an organization, ensure it has globally unique usernames
|
||||
// and create a user with a loginname that matches the domain later on
|
||||
organization, err := Client.CreateOrganization(CTX, &v2beta_org.CreateOrganizationRequest{
|
||||
Name: gofakeit.AppName(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = Instance.Client.Admin.AddCustomDomainPolicy(CTX, &admin.AddCustomDomainPolicyRequest{
|
||||
OrgId: organization.GetId(),
|
||||
UserLoginMustBeDomain: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
username := gofakeit.Username() + "@" + domain
|
||||
ownUser := Instance.CreateHumanUserVerified(CTX, organization.GetId(), username, "")
|
||||
|
||||
// create another organization, ensure it has globally unique usernames
|
||||
// and create a user with a loginname that matches the domain later on
|
||||
otherOrg, err := Client.CreateOrganization(CTX, &v2beta_org.CreateOrganizationRequest{
|
||||
Name: gofakeit.AppName(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = Instance.Client.Admin.AddCustomDomainPolicy(CTX, &admin.AddCustomDomainPolicyRequest{
|
||||
OrgId: otherOrg.GetId(),
|
||||
UserLoginMustBeDomain: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
otherUsername := gofakeit.Username() + "@" + domain
|
||||
otherUser := Instance.CreateHumanUserVerified(CTX, otherOrg.GetId(), otherUsername, "")
|
||||
|
||||
// if we add the domain now to the first organization, it should be claimed on the second organization, resp. its user(s)
|
||||
_, err = Client.AddOrganizationDomain(CTX, &v2beta_org.AddOrganizationDomainRequest{
|
||||
OrganizationId: organization.GetId(),
|
||||
Domain: domain,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// check both users: the first one must be untouched, the second one must be updated
|
||||
users, err := Instance.Client.UserV2.ListUsers(CTX, &user.ListUsersRequest{
|
||||
Queries: []*user.SearchQuery{
|
||||
{
|
||||
Query: &user.SearchQuery_InUserIdsQuery{
|
||||
InUserIdsQuery: &user.InUserIDQuery{UserIds: []string{ownUser.GetUserId(), otherUser.GetUserId()}},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, users.GetResult(), 2)
|
||||
|
||||
for _, u := range users.GetResult() {
|
||||
if u.GetUserId() == ownUser.GetUserId() {
|
||||
assert.Equal(t, username, u.GetPreferredLoginName())
|
||||
continue
|
||||
}
|
||||
if u.GetUserId() == otherUser.GetUserId() {
|
||||
assert.NotEqual(t, otherUsername, u.GetPreferredLoginName())
|
||||
assert.Contains(t, u.GetPreferredLoginName(), "@temporary.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListOrganizationDomains(t *testing.T) {
|
||||
domain := gofakeit.URL()
|
||||
tests := []struct {
|
||||
|
@@ -250,26 +250,5 @@ func createOrganizationRequestAdminToCommand(admin *v2beta_org.CreateOrganizatio
|
||||
}
|
||||
|
||||
func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain, orgID string) ([]string, error) {
|
||||
queries := make([]query.SearchQuery, 0, 2)
|
||||
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+orgDomain, query.TextEndsWithIgnoreCase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, loginName)
|
||||
if orgID != "" {
|
||||
owner, err := query.NewUserResourceOwnerSearchQuery(orgID, query.TextNotEquals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, owner)
|
||||
}
|
||||
users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: queries}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs := make([]string, len(users.Users))
|
||||
for i, user := range users.Users {
|
||||
userIDs[i] = user.ID
|
||||
}
|
||||
return userIDs, nil
|
||||
return s.query.SearchClaimedUserIDsOfOrgDomain(ctx, orgDomain, orgID)
|
||||
}
|
||||
|
@@ -10,30 +10,36 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Authorization = "authorization"
|
||||
Accept = "accept"
|
||||
AcceptLanguage = "accept-language"
|
||||
CacheControl = "cache-control"
|
||||
ContentType = "content-type"
|
||||
ContentLength = "content-length"
|
||||
ContentLocation = "content-location"
|
||||
Expires = "expires"
|
||||
Location = "location"
|
||||
Origin = "origin"
|
||||
Pragma = "pragma"
|
||||
UserAgentHeader = "user-agent"
|
||||
ForwardedFor = "x-forwarded-for"
|
||||
ForwardedHost = "x-forwarded-host"
|
||||
ForwardedProto = "x-forwarded-proto"
|
||||
Forwarded = "forwarded"
|
||||
ZitadelForwarded = "x-zitadel-forwarded"
|
||||
XUserAgent = "x-user-agent"
|
||||
XGrpcWeb = "x-grpc-web"
|
||||
XRequestedWith = "x-requested-with"
|
||||
XRobotsTag = "x-robots-tag"
|
||||
IfNoneMatch = "If-None-Match"
|
||||
LastModified = "Last-Modified"
|
||||
Etag = "Etag"
|
||||
Authorization = "authorization"
|
||||
Accept = "accept"
|
||||
AcceptLanguage = "accept-language"
|
||||
CacheControl = "cache-control"
|
||||
ContentType = "content-type"
|
||||
ContentLength = "content-length"
|
||||
ContentLocation = "content-location"
|
||||
Expires = "expires"
|
||||
Location = "location"
|
||||
Origin = "origin"
|
||||
Pragma = "pragma"
|
||||
UserAgentHeader = "user-agent"
|
||||
ForwardedFor = "x-forwarded-for"
|
||||
ForwardedHost = "x-forwarded-host"
|
||||
ForwardedProto = "x-forwarded-proto"
|
||||
Forwarded = "forwarded"
|
||||
ZitadelForwarded = "x-zitadel-forwarded"
|
||||
XUserAgent = "x-user-agent"
|
||||
XGrpcWeb = "x-grpc-web"
|
||||
XRequestedWith = "x-requested-with"
|
||||
XRobotsTag = "x-robots-tag"
|
||||
IfNoneMatch = "if-none-match"
|
||||
LastModified = "last-modified"
|
||||
Etag = "etag"
|
||||
GRPCTimeout = "grpc-timeout"
|
||||
ConnectProtocolVersion = "connect-protocol-version"
|
||||
ConnectTimeoutMS = "connect-timeout-ms"
|
||||
GrpcStatus = "grpc-status"
|
||||
GrpcMessage = "grpc-message"
|
||||
GrpcStatusDetailsBin = "grpc-status-details-bin"
|
||||
|
||||
ContentSecurityPolicy = "content-security-policy"
|
||||
XXSSProtection = "x-xss-protection"
|
||||
|
@@ -21,6 +21,9 @@ var (
|
||||
http_utils.XUserAgent,
|
||||
http_utils.XGrpcWeb,
|
||||
http_utils.XRequestedWith,
|
||||
http_utils.ConnectProtocolVersion,
|
||||
http_utils.ConnectTimeoutMS,
|
||||
http_utils.GRPCTimeout,
|
||||
},
|
||||
AllowedMethods: []string{
|
||||
http.MethodOptions,
|
||||
@@ -34,6 +37,9 @@ var (
|
||||
ExposedHeaders: []string{
|
||||
http_utils.Location,
|
||||
http_utils.ContentLength,
|
||||
http_utils.GrpcStatus,
|
||||
http_utils.GrpcMessage,
|
||||
http_utils.GrpcStatusDetailsBin,
|
||||
},
|
||||
AllowOriginFunc: func(_ string) bool {
|
||||
return true
|
||||
|
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||
@@ -30,6 +31,8 @@ import (
|
||||
const (
|
||||
LoginClientHeader = "x-zitadel-login-client"
|
||||
LoginPostLogoutRedirectParam = "post_logout_redirect"
|
||||
LoginLogoutHintParam = "logout_hint"
|
||||
LoginUILocalesParam = "ui_locales"
|
||||
LoginPath = "/login"
|
||||
LogoutPath = "/logout"
|
||||
LogoutDonePath = "/logout/done"
|
||||
@@ -283,14 +286,19 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
|
||||
// we'll redirect to the UI (V2) and let it decide which session to terminate
|
||||
//
|
||||
// If there's no id_token_hint and for v1 logins, we handle them separately
|
||||
if endSessionRequest.IDTokenHintClaims == nil &&
|
||||
(authz.GetFeatures(ctx).LoginV2.Required || headers.Get(LoginClientHeader) != "") {
|
||||
if endSessionRequest.IDTokenHintClaims == nil && (authz.GetFeatures(ctx).LoginV2.Required || headers.Get(LoginClientHeader) != "") {
|
||||
redirectURI := v2PostLogoutRedirectURI(endSessionRequest.RedirectURI)
|
||||
// if no base uri is set, fallback to the default configured in the runtime config
|
||||
if authz.GetFeatures(ctx).LoginV2.BaseURI == nil || authz.GetFeatures(ctx).LoginV2.BaseURI.String() == "" {
|
||||
return o.defaultLogoutURLV2 + redirectURI, nil
|
||||
logoutURI := authz.GetFeatures(ctx).LoginV2.BaseURI
|
||||
// if no logout uri is set, fallback to the default configured in the runtime config
|
||||
if logoutURI == nil || logoutURI.String() == "" {
|
||||
logoutURI, err = url.Parse(o.defaultLogoutURLV2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
logoutURI = logoutURI.JoinPath(LogoutPath)
|
||||
}
|
||||
return buildLoginV2LogoutURL(authz.GetFeatures(ctx).LoginV2.BaseURI, redirectURI), nil
|
||||
return buildLoginV2LogoutURL(logoutURI, redirectURI, endSessionRequest.LogoutHint, endSessionRequest.UILocales), nil
|
||||
}
|
||||
|
||||
// V1:
|
||||
@@ -367,12 +375,25 @@ func (o *OPStorage) federatedLogout(ctx context.Context, sessionID string, postL
|
||||
return login.ExternalLogoutPath(sessionID)
|
||||
}
|
||||
|
||||
func buildLoginV2LogoutURL(baseURI *url.URL, redirectURI string) string {
|
||||
baseURI.JoinPath(LogoutPath)
|
||||
q := baseURI.Query()
|
||||
func buildLoginV2LogoutURL(logoutURI *url.URL, redirectURI, logoutHint string, uiLocales []language.Tag) string {
|
||||
if strings.HasSuffix(logoutURI.Path, "/") && len(logoutURI.Path) > 1 {
|
||||
logoutURI.Path = strings.TrimSuffix(logoutURI.Path, "/")
|
||||
}
|
||||
|
||||
q := logoutURI.Query()
|
||||
q.Set(LoginPostLogoutRedirectParam, redirectURI)
|
||||
baseURI.RawQuery = q.Encode()
|
||||
return baseURI.String()
|
||||
if logoutHint != "" {
|
||||
q.Set(LoginLogoutHintParam, logoutHint)
|
||||
}
|
||||
if len(uiLocales) > 0 {
|
||||
locales := make([]string, len(uiLocales))
|
||||
for i, locale := range uiLocales {
|
||||
locales[i] = locale.String()
|
||||
}
|
||||
q.Set(LoginUILocalesParam, strings.Join(locales, " "))
|
||||
}
|
||||
logoutURI.RawQuery = q.Encode()
|
||||
return logoutURI.String()
|
||||
}
|
||||
|
||||
// v2PostLogoutRedirectURI will take care that the post_logout_redirect_uri is correctly set for v2 logins.
|
||||
|
98
internal/api/oidc/auth_request_test.go
Normal file
98
internal/api/oidc/auth_request_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func TestBuildLoginV2LogoutURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
logoutURIStr string
|
||||
redirectURI string
|
||||
logoutHint string
|
||||
uiLocales []language.Tag
|
||||
expectedParams map[string]string
|
||||
}{
|
||||
{
|
||||
testName: "basic with only redirectURI",
|
||||
logoutURIStr: "https://example.com/logout",
|
||||
redirectURI: "https://client/cb",
|
||||
expectedParams: map[string]string{
|
||||
"post_logout_redirect": "https://client/cb",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "with logout hint",
|
||||
logoutURIStr: "https://example.com/logout",
|
||||
redirectURI: "https://client/cb",
|
||||
logoutHint: "user@example.com",
|
||||
expectedParams: map[string]string{
|
||||
"post_logout_redirect": "https://client/cb",
|
||||
"logout_hint": "user@example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "with ui_locales",
|
||||
logoutURIStr: "https://example.com/logout",
|
||||
redirectURI: "https://client/cb",
|
||||
uiLocales: []language.Tag{language.English, language.Italian},
|
||||
expectedParams: map[string]string{
|
||||
"post_logout_redirect": "https://client/cb",
|
||||
"ui_locales": "en it",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "with all params",
|
||||
logoutURIStr: "https://example.com/logout",
|
||||
redirectURI: "https://client/cb",
|
||||
logoutHint: "logoutme",
|
||||
uiLocales: []language.Tag{language.Make("de-CH"), language.Make("fr")},
|
||||
expectedParams: map[string]string{
|
||||
"post_logout_redirect": "https://client/cb",
|
||||
"logout_hint": "logoutme",
|
||||
"ui_locales": "de-CH fr",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "base with trailing slash",
|
||||
logoutURIStr: "https://example.com/logout/",
|
||||
redirectURI: "https://client/cb",
|
||||
expectedParams: map[string]string{
|
||||
"post_logout_redirect": "https://client/cb",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// Given
|
||||
logoutURI, err := url.Parse(tc.logoutURIStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
// When
|
||||
got := buildLoginV2LogoutURL(logoutURI, tc.redirectURI, tc.logoutHint, tc.uiLocales)
|
||||
|
||||
// Then
|
||||
gotURL, err := url.Parse(got)
|
||||
require.NoError(t, err)
|
||||
require.NotContains(t, gotURL.String(), "/logout/")
|
||||
|
||||
q := gotURL.Query()
|
||||
// Ensure no unexpected params
|
||||
require.Len(t, q, len(tc.expectedParams))
|
||||
|
||||
for k, v := range tc.expectedParams {
|
||||
assert.Equal(t, v, q.Get(k))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -498,7 +498,7 @@ func TestOPStorage_TerminateSession(t *testing.T) {
|
||||
_, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
|
||||
require.NoError(t, err)
|
||||
|
||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state")
|
||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String())
|
||||
|
||||
@@ -535,7 +535,7 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
|
||||
_, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
|
||||
require.NoError(t, err)
|
||||
|
||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state")
|
||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String())
|
||||
|
||||
@@ -551,6 +551,17 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func buildLogoutURL(origin, logoutURLV2 string, redirectURI string, extraParams map[string]string) string {
|
||||
u, _ := url.Parse(origin + logoutURLV2 + redirectURI)
|
||||
q := u.Query()
|
||||
for k, v := range extraParams {
|
||||
q.Set(k, v)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
// Append the redirect URI as a URL-escaped string
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -565,7 +576,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
|
||||
return clientID
|
||||
}(),
|
||||
authRequestID: createAuthRequest,
|
||||
logoutURL: http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure) + Instance.Config.LogoutURLV2 + logoutRedirectURI + "?state=state",
|
||||
logoutURL: buildLogoutURL(http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure), Instance.Config.LogoutURLV2, logoutRedirectURI+"?state=state", map[string]string{"logout_hint": "hint", "ui_locales": "it-IT en-US"}),
|
||||
},
|
||||
{
|
||||
name: "login v2 config",
|
||||
@@ -574,7 +585,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
|
||||
return clientID
|
||||
}(),
|
||||
authRequestID: createAuthRequestNoLoginClientHeader,
|
||||
logoutURL: http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure) + Instance.Config.LogoutURLV2 + logoutRedirectURI + "?state=state",
|
||||
logoutURL: buildLogoutURL(http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure), Instance.Config.LogoutURLV2, logoutRedirectURI+"?state=state", map[string]string{"logout_hint": "hint", "ui_locales": "it-IT en-US"}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -601,7 +612,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
|
||||
assertTokens(t, tokens, false)
|
||||
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
||||
|
||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, "", logoutRedirectURI, "state")
|
||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, "", logoutRedirectURI, "state", "hint", oidc.ParseLocales([]string{"it-IT", "en-US"}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.logoutURL, postLogoutRedirect.String())
|
||||
|
||||
|
@@ -311,7 +311,7 @@ func Test_ZITADEL_API_terminated_session(t *testing.T) {
|
||||
require.Equal(t, User.GetUserId(), myUserResp.GetUser().GetId())
|
||||
|
||||
// end session
|
||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state")
|
||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String())
|
||||
|
||||
|
@@ -100,6 +100,7 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR
|
||||
token.userID,
|
||||
token.scope,
|
||||
client.projectID,
|
||||
client.clientID,
|
||||
client.projectRoleAssertion,
|
||||
true,
|
||||
true,
|
||||
|
@@ -31,7 +31,7 @@ for example the v2 code exchange and refresh token.
|
||||
*/
|
||||
|
||||
func (s *Server) accessTokenResponseFromSession(ctx context.Context, client op.Client, session *command.OIDCSession, state, projectID string, projectRoleAssertion, accessTokenRoleAssertion, idTokenRoleAssertion, userInfoAssertion bool) (_ *oidc.AccessTokenResponse, err error) {
|
||||
getUserInfo := s.getUserInfo(session.UserID, projectID, projectRoleAssertion, userInfoAssertion, session.Scope)
|
||||
getUserInfo := s.getUserInfo(session.UserID, projectID, client.GetID(), projectRoleAssertion, userInfoAssertion, session.Scope)
|
||||
getSigner := s.getSignerOnce()
|
||||
|
||||
resp := &oidc.AccessTokenResponse{
|
||||
@@ -113,8 +113,8 @@ type userInfoFunc func(ctx context.Context, roleAssertion bool, triggerType doma
|
||||
|
||||
// getUserInfo returns a function which retrieves userinfo from the database once.
|
||||
// However, each time, role claims are asserted and also action flows will trigger.
|
||||
func (s *Server) getUserInfo(userID, projectID string, projectRoleAssertion, userInfoAssertion bool, scope []string) userInfoFunc {
|
||||
userInfo := s.userInfo(userID, scope, projectID, projectRoleAssertion, userInfoAssertion, false)
|
||||
func (s *Server) getUserInfo(userID, projectID, clientID string, projectRoleAssertion, userInfoAssertion bool, scope []string) userInfoFunc {
|
||||
userInfo := s.userInfo(userID, scope, projectID, clientID, projectRoleAssertion, userInfoAssertion, false)
|
||||
return func(ctx context.Context, roleAssertion bool, triggerType domain.TriggerType) (*oidc.UserInfo, error) {
|
||||
return userInfo(ctx, roleAssertion, triggerType)
|
||||
}
|
||||
|
@@ -218,7 +218,7 @@ func validateTokenExchangeAudience(requestedAudience, subjectAudience, actorAudi
|
||||
// Both tokens may point to the same object (subjectToken) in case of a regular Token Exchange.
|
||||
// When the subject and actor Tokens point to different objects, the new tokens will be for impersonation / delegation.
|
||||
func (s *Server) createExchangeTokens(ctx context.Context, tokenType oidc.TokenType, client *Client, subjectToken, actorToken *exchangeToken, audience, scopes []string) (_ *oidc.TokenExchangeResponse, err error) {
|
||||
getUserInfo := s.getUserInfo(subjectToken.userID, client.client.ProjectID, client.client.ProjectRoleAssertion, client.IDTokenUserinfoClaimsAssertion(), scopes)
|
||||
getUserInfo := s.getUserInfo(subjectToken.userID, client.client.ProjectID, client.GetID(), client.client.ProjectRoleAssertion, client.IDTokenUserinfoClaimsAssertion(), scopes)
|
||||
getSigner := s.getSignerOnce()
|
||||
|
||||
resp := &oidc.TokenExchangeResponse{
|
||||
|
@@ -54,6 +54,7 @@ func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoReques
|
||||
token.userID,
|
||||
token.scope,
|
||||
projectID,
|
||||
token.clientID,
|
||||
assertion,
|
||||
true,
|
||||
false,
|
||||
@@ -86,6 +87,7 @@ func (s *Server) userInfo(
|
||||
userID string,
|
||||
scope []string,
|
||||
projectID string,
|
||||
clientID string,
|
||||
projectRoleAssertion, userInfoAssertion, currentProjectOnly bool,
|
||||
) func(ctx context.Context, roleAssertion bool, triggerType domain.TriggerType) (_ *oidc.UserInfo, err error) {
|
||||
var (
|
||||
@@ -120,7 +122,7 @@ func (s *Server) userInfo(
|
||||
Claims: maps.Clone(rawUserInfo.Claims),
|
||||
}
|
||||
assertRoles(projectID, qu, roleAudience, requestedRoles, roleAssertion, userInfo)
|
||||
return userInfo, s.userinfoFlows(ctx, qu, userInfo, triggerType)
|
||||
return userInfo, s.userinfoFlows(ctx, qu, userInfo, triggerType, clientID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +287,8 @@ func setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo, triggerType domain.TriggerType) (err error) {
|
||||
//nolint:gocognit
|
||||
func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo, triggerType domain.TriggerType, clientID string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -319,6 +322,13 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
|
||||
}
|
||||
}),
|
||||
),
|
||||
actions.SetFields("application",
|
||||
actions.SetFields("getClientId", func(c *actions.FieldConfig) interface{} {
|
||||
return func(goja.FunctionCall) goja.Value {
|
||||
return c.Runtime.ToValue(clientID)
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -427,6 +437,7 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
|
||||
User: qu.User,
|
||||
UserMetadata: qu.Metadata,
|
||||
Org: qu.Org,
|
||||
Application: &ContextInfoApplication{ClientID: clientID},
|
||||
UserGrants: qu.UserGrants,
|
||||
}
|
||||
|
||||
@@ -463,13 +474,17 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
|
||||
}
|
||||
|
||||
type ContextInfo struct {
|
||||
Function string `json:"function,omitempty"`
|
||||
UserInfo *oidc.UserInfo `json:"userinfo,omitempty"`
|
||||
User *query.User `json:"user,omitempty"`
|
||||
UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"`
|
||||
Org *query.UserInfoOrg `json:"org,omitempty"`
|
||||
UserGrants []query.UserGrant `json:"user_grants,omitempty"`
|
||||
Response *ContextInfoResponse `json:"response,omitempty"`
|
||||
Function string `json:"function,omitempty"`
|
||||
UserInfo *oidc.UserInfo `json:"userinfo,omitempty"`
|
||||
User *query.User `json:"user,omitempty"`
|
||||
UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"`
|
||||
Org *query.UserInfoOrg `json:"org,omitempty"`
|
||||
UserGrants []query.UserGrant `json:"user_grants,omitempty"`
|
||||
Application *ContextInfoApplication `json:"application,omitempty"`
|
||||
Response *ContextInfoResponse `json:"response,omitempty"`
|
||||
}
|
||||
type ContextInfoApplication struct {
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
}
|
||||
|
||||
type ContextInfoResponse struct {
|
||||
|
@@ -432,3 +432,78 @@ func TestCreateUser_scopedExternalID(t *testing.T) {
|
||||
assert.Equal(tt, "701984", string(md.Metadata.Value))
|
||||
}, retryDuration, tick)
|
||||
}
|
||||
|
||||
func TestCreateUser_ignorePasswordOnCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
ignorePassword string
|
||||
scimErrorType string
|
||||
scimErrorDetail string
|
||||
wantUser *resources.ScimUser
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ignorePasswordOnCreate set to false",
|
||||
ignorePassword: "false",
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
scimErrorDetail: "Password is too short",
|
||||
},
|
||||
{
|
||||
name: "ignorePasswordOnCreate set to an invalid value",
|
||||
ignorePassword: "random",
|
||||
wantErr: true,
|
||||
scimErrorType: "invalidValue",
|
||||
scimErrorDetail: "Invalid value for metadata key urn:zitadel:scim:ignorePasswordOnCreate: random",
|
||||
},
|
||||
{
|
||||
name: "ignorePasswordOnCreate set to true",
|
||||
ignorePassword: "true",
|
||||
wantUser: &resources.ScimUser{
|
||||
UserName: "acmeUser1",
|
||||
Name: &resources.ScimUserName{
|
||||
FamilyName: "Ross",
|
||||
GivenName: "Bethany",
|
||||
},
|
||||
Emails: []*resources.ScimEmail{
|
||||
{
|
||||
Value: "user1@example.com",
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// create a machine user
|
||||
callingUserId, callingUserPat, err := Instance.CreateMachineUserPATWithMembership(CTX, "ORG_OWNER")
|
||||
require.NoError(t, err)
|
||||
ctx := integration.WithAuthorizationToken(CTX, callingUserPat)
|
||||
|
||||
// set urn:zitadel:scim:ignorePasswordOnCreate metadata for the machine user
|
||||
setAndEnsureMetadata(t, callingUserId, "urn:zitadel:scim:ignorePasswordOnCreate", tt.ignorePassword)
|
||||
|
||||
// create a user with an invalid password
|
||||
createdUser, err := Instance.Client.SCIM.Users.Create(ctx, Instance.DefaultOrg.Id, withUsername(invalidPasswordUserJson, "acmeUser1"))
|
||||
require.Equal(t, tt.wantErr, err != nil)
|
||||
if err != nil {
|
||||
scimErr := scim.RequireScimError(t, http.StatusBadRequest, err)
|
||||
assert.Equal(t, tt.scimErrorType, scimErr.Error.ScimType)
|
||||
assert.Equal(t, tt.scimErrorDetail, scimErr.Error.Detail)
|
||||
return
|
||||
}
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// ensure the user is really stored and not just returned to the caller
|
||||
fetchedUser, err := Instance.Client.SCIM.Users.Get(CTX, Instance.DefaultOrg.Id, createdUser.ID)
|
||||
require.NoError(ttt, err)
|
||||
assert.True(ttt, test.PartiallyDeepEqual(tt.wantUser, fetchedUser))
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ var scimContextKey scimContextKeyType
|
||||
|
||||
type ScimContextData struct {
|
||||
ProvisioningDomain string
|
||||
IgnorePasswordOnCreate bool
|
||||
ExternalIDScopedMetadataKey ScopedKey
|
||||
bulkIDMapping map[string]string
|
||||
}
|
||||
|
@@ -13,8 +13,9 @@ type ScopedKey string
|
||||
const (
|
||||
externalIdProvisioningDomainPlaceholder = "{provisioningDomain}"
|
||||
|
||||
KeyPrefix = "urn:zitadel:scim:"
|
||||
KeyProvisioningDomain Key = KeyPrefix + "provisioningDomain"
|
||||
KeyPrefix = "urn:zitadel:scim:"
|
||||
KeyProvisioningDomain Key = KeyPrefix + "provisioningDomain"
|
||||
KeyIgnorePasswordOnCreate Key = KeyPrefix + "ignorePasswordOnCreate"
|
||||
|
||||
KeyExternalId Key = KeyPrefix + "externalId"
|
||||
keyScopedExternalIdTemplate = KeyPrefix + externalIdProvisioningDomainPlaceholder + ":externalId"
|
||||
|
@@ -3,10 +3,15 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
zhttp "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
smetadata "github.com/zitadel/zitadel/internal/api/scim/metadata"
|
||||
sresources "github.com/zitadel/zitadel/internal/api/scim/resources"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@@ -29,22 +34,43 @@ func initScimContext(ctx context.Context, q *query.Queries) (context.Context, er
|
||||
ctx = smetadata.SetScimContextData(ctx, data)
|
||||
|
||||
userID := authz.GetCtxData(ctx).UserID
|
||||
metadata, err := q.GetUserMetadataByKey(ctx, false, userID, string(smetadata.KeyProvisioningDomain), false)
|
||||
|
||||
// get the provisioningDomain and ignorePasswordOnCreate metadata keys associated with the service user
|
||||
metadataKeys := []smetadata.Key{
|
||||
smetadata.KeyProvisioningDomain,
|
||||
smetadata.KeyIgnorePasswordOnCreate,
|
||||
}
|
||||
queries := sresources.BuildMetadataQueries(ctx, metadataKeys)
|
||||
|
||||
metadataList, err := q.SearchUserMetadata(ctx, false, userID, queries, nil)
|
||||
if err != nil {
|
||||
if zerrors.IsNotFound(err) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
if metadata == nil {
|
||||
if metadataList == nil || len(metadataList.Metadata) == 0 {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
data.ProvisioningDomain = string(metadata.Value)
|
||||
if data.ProvisioningDomain != "" {
|
||||
data.ExternalIDScopedMetadataKey = smetadata.ScopeExternalIdKey(data.ProvisioningDomain)
|
||||
for _, metadata := range metadataList.Metadata {
|
||||
switch metadata.Key {
|
||||
case string(smetadata.KeyProvisioningDomain):
|
||||
data.ProvisioningDomain = string(metadata.Value)
|
||||
if data.ProvisioningDomain != "" {
|
||||
data.ExternalIDScopedMetadataKey = smetadata.ScopeExternalIdKey(data.ProvisioningDomain)
|
||||
}
|
||||
case string(smetadata.KeyIgnorePasswordOnCreate):
|
||||
ignorePasswordOnCreate, parseErr := strconv.ParseBool(strings.TrimSpace(string(metadata.Value)))
|
||||
if parseErr != nil {
|
||||
return ctx,
|
||||
zerrors.ThrowInvalidArgumentf(nil, "SMCM-yvw2rt", "Invalid value for metadata key %s: %s", smetadata.KeyIgnorePasswordOnCreate, metadata.Value)
|
||||
}
|
||||
data.IgnorePasswordOnCreate = ignorePasswordOnCreate
|
||||
default:
|
||||
logging.WithFields("user_metadata_key", metadata.Key).Warn("unexpected metadata key")
|
||||
}
|
||||
}
|
||||
return smetadata.SetScimContextData(ctx, data), nil
|
||||
}
|
||||
|
@@ -45,7 +45,12 @@ func (h *UsersHandler) mapToAddHuman(ctx context.Context, scimUser *ScimUser) (*
|
||||
}
|
||||
human.Metadata = md
|
||||
|
||||
if scimUser.Password != nil {
|
||||
// Okta sends a random password during SCIM provisioning
|
||||
// irrespective of whether the Sync Password option is enabled or disabled on Okta.
|
||||
// This password does not comply with Zitadel's password complexity, and
|
||||
// the following workaround ignores the random password as it does not add any value.
|
||||
ignorePasswordOnCreate := metadata.GetScimContextData(ctx).IgnorePasswordOnCreate
|
||||
if scimUser.Password != nil && !ignorePasswordOnCreate {
|
||||
human.Password = scimUser.Password.String()
|
||||
scimUser.Password = nil
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
func (h *UsersHandler) queryMetadataForUsers(ctx context.Context, userIds []string) (map[string]map[metadata.ScopedKey][]byte, error) {
|
||||
queries := h.buildMetadataQueries(ctx)
|
||||
queries := BuildMetadataQueries(ctx, metadata.ScimUserRelevantMetadataKeys)
|
||||
|
||||
md, err := h.query.SearchUserMetadataForUsers(ctx, false, userIds, queries)
|
||||
if err != nil {
|
||||
@@ -43,7 +43,7 @@ func (h *UsersHandler) queryMetadataForUsers(ctx context.Context, userIds []stri
|
||||
}
|
||||
|
||||
func (h *UsersHandler) queryMetadataForUser(ctx context.Context, id string) (map[metadata.ScopedKey][]byte, error) {
|
||||
queries := h.buildMetadataQueries(ctx)
|
||||
queries := BuildMetadataQueries(ctx, metadata.ScimUserRelevantMetadataKeys)
|
||||
|
||||
md, err := h.query.SearchUserMetadata(ctx, false, id, queries, nil)
|
||||
if err != nil {
|
||||
@@ -53,9 +53,9 @@ func (h *UsersHandler) queryMetadataForUser(ctx context.Context, id string) (map
|
||||
return metadata.MapListToScopedKeyMap(md.Metadata), nil
|
||||
}
|
||||
|
||||
func (h *UsersHandler) buildMetadataQueries(ctx context.Context) *query.UserMetadataSearchQueries {
|
||||
keyQueries := make([]query.SearchQuery, len(metadata.ScimUserRelevantMetadataKeys))
|
||||
for i, key := range metadata.ScimUserRelevantMetadataKeys {
|
||||
func BuildMetadataQueries(ctx context.Context, metadataKeys []metadata.Key) *query.UserMetadataSearchQueries {
|
||||
keyQueries := make([]query.SearchQuery, len(metadataKeys))
|
||||
for i, key := range metadataKeys {
|
||||
keyQueries[i] = buildMetadataKeyQuery(ctx, key)
|
||||
}
|
||||
|
||||
|
@@ -523,7 +523,7 @@ func (l *Login) handleExternalUserAuthenticated(
|
||||
// The decision, which information will be checked is based on the IdP template option.
|
||||
// The function returns a boolean whether a user was found or not.
|
||||
// If single a user was found, it will be automatically linked.
|
||||
func (l *Login) checkAutoLinking(r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser) (bool, error) {
|
||||
func (l *Login) checkAutoLinking(r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser, human *domain.Human) (bool, error) {
|
||||
queries := make([]query.SearchQuery, 0, 2)
|
||||
switch provider.AutoLinking {
|
||||
case domain.AutoLinkingOptionUnspecified:
|
||||
@@ -532,7 +532,7 @@ func (l *Login) checkAutoLinking(r *http.Request, authReq *domain.AuthRequest, p
|
||||
case domain.AutoLinkingOptionUsername:
|
||||
// if we're checking for usernames there are to options:
|
||||
//
|
||||
// If no specific org has been requested (by id or domain scope), we'll check the provided username against
|
||||
// If no specific org has been requested (by id or domain scope), we'll check the provided username (loginname) against
|
||||
// all existing loginnames and directly use that result to either prompt or continue with other idp options.
|
||||
if authReq.RequestedOrgID == "" {
|
||||
user, err := l.query.GetNotifyUserByLoginName(r.Context(), false, externalUser.PreferredUsername)
|
||||
@@ -544,8 +544,9 @@ func (l *Login) checkAutoLinking(r *http.Request, authReq *domain.AuthRequest, p
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
// If a specific org has been requested, we'll check the provided username against usernames (of that org).
|
||||
usernameQuery, err := query.NewUserUsernameSearchQuery(externalUser.PreferredUsername, query.TextEqualsIgnoreCase)
|
||||
// If a specific org has been requested, we'll check the username (org policy (suffixed or not) is already applied)
|
||||
// against usernames (of that org).
|
||||
usernameQuery, err := query.NewUserUsernameSearchQuery(human.Username, query.TextEqualsIgnoreCase)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
@@ -605,7 +606,7 @@ func (l *Login) createOrLinkUser(w http.ResponseWriter, r *http.Request, authReq
|
||||
human, idpLink, _ := mapExternalUserToLoginUser(externalUser, orgIAMPolicy.UserLoginMustBeDomain)
|
||||
// let's check if auto-linking is enabled and if the user would be found by the corresponding option
|
||||
if provider.AutoLinking != domain.AutoLinkingOptionUnspecified {
|
||||
userLinked, err = l.checkAutoLinking(r, authReq, provider, externalUser)
|
||||
userLinked, err = l.checkAutoLinking(r, authReq, provider, externalUser, human)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return false
|
||||
|
@@ -178,19 +178,7 @@ func (l *Login) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgName string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+orgDomain, query.TextEndsWithIgnoreCase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users, err := l.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs := make([]string, len(users.Users))
|
||||
for i, user := range users.Users {
|
||||
userIDs[i] = user.ID
|
||||
}
|
||||
return userIDs, nil
|
||||
return l.query.SearchClaimedUserIDsOfOrgDomain(ctx, orgDomain, "")
|
||||
}
|
||||
|
||||
func setContext(ctx context.Context, resourceOwner string) context.Context {
|
||||
|
@@ -17,7 +17,11 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
user, err := l.query.GetUserByLoginName(setContext(r.Context(), authReq.UserOrgID), true, authReq.LoginName)
|
||||
// We check if the user really exists or if it is just a placeholder or an unknown user.
|
||||
// In theory, we could also check for the unknownUserID constant. However, that could disclose
|
||||
// information about the existence of a user to an attacker if they check response times,
|
||||
// since those requests would take shorter than the ones for real users.
|
||||
user, err := l.query.GetUserByID(setContext(r.Context(), authReq.UserOrgID), true, authReq.UserID)
|
||||
if err != nil {
|
||||
if authReq.LoginPolicy.IgnoreUnknownUsernames && zerrors.IsNotFound(err) {
|
||||
err = nil
|
||||
|
@@ -262,6 +262,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Пол
|
||||
Female: Женски пол
|
||||
Male: Мъжки
|
||||
@@ -305,6 +306,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Правила и условия
|
||||
TosConfirm: Приемам
|
||||
TosLinkText: TOS
|
||||
@@ -377,6 +379,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Упълномощаване на устройството
|
||||
UserCode:
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Pohlaví
|
||||
Female: Žena
|
||||
Male: Muž
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Obchodní podmínky
|
||||
TosConfirm: Souhlasím s
|
||||
TosLinkText: obchodními podmínkami
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Autorizace zařízení
|
||||
UserCode:
|
||||
|
@@ -265,6 +265,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Geschlecht
|
||||
Female: weiblich
|
||||
Male: männlich
|
||||
@@ -309,6 +310,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz
|
||||
TosConfirm: Ich akzeptiere die
|
||||
TosLinkText: AGB
|
||||
@@ -387,6 +389,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Gerät verbinden
|
||||
UserCode:
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Gender
|
||||
Female: Female
|
||||
Male: Male
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Terms and conditions
|
||||
TosConfirm: I accept the
|
||||
TosLinkText: TOS
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Device Authorization
|
||||
UserCode:
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Género
|
||||
Female: Mujer
|
||||
Male: Hombre
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Términos y condiciones
|
||||
TosConfirm: Acepto los
|
||||
TosLinkText: TDS
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
|
||||
Footer:
|
||||
PoweredBy: Powered By
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Genre
|
||||
Female: Femme
|
||||
Male: Homme
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Termes et conditions
|
||||
TosConfirm: J'accepte les
|
||||
TosLinkText: TOS
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorisation de l'appareil
|
||||
|
@@ -236,6 +236,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Nem
|
||||
Female: Nő
|
||||
Male: Férfi
|
||||
@@ -279,6 +280,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Felhasználási feltételek
|
||||
TosConfirm: Elfogadom a
|
||||
TosLinkText: TOS
|
||||
@@ -351,6 +353,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Eszköz engedélyezése
|
||||
UserCode:
|
||||
|
@@ -236,6 +236,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Jenis kelamin
|
||||
Female: Perempuan
|
||||
Male: Pria
|
||||
@@ -279,6 +280,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
@@ -351,6 +353,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Otorisasi Perangkat
|
||||
UserCode:
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Genere
|
||||
Female: Femminile
|
||||
Male: Maschile
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Termini di servizio
|
||||
TosConfirm: Accetto i
|
||||
TosLinkText: Termini di servizio
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorizzazione del dispositivo
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: 性別
|
||||
Female: 女性
|
||||
Male: 男性
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: 利用規約
|
||||
TosConfirm: 私は利用規約を承諾します。
|
||||
TosLinkText: TOS
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
|
||||
DeviceAuth:
|
||||
Title: デバイス認証
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: 성별
|
||||
Female: 여성
|
||||
Male: 남성
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: 동의사항
|
||||
TosConfirm: 이용 약관에 동의합니다.
|
||||
TosLinkText: 이용 약관
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: 기기 인증
|
||||
UserCode:
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Пол
|
||||
Female: Женски
|
||||
Male: Машки
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Правила и услови
|
||||
TosConfirm: Се согласувам со
|
||||
TosLinkText: правилата за користење
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
|
||||
DeviceAuth:
|
||||
Title: Овластување преку уред
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Geslacht
|
||||
Female: Vrouw
|
||||
Male: Man
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Algemene voorwaarden
|
||||
TosConfirm: Ik accepteer de
|
||||
TosLinkText: AV
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Apparaat Autorisatie
|
||||
UserCode:
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Płeć
|
||||
Female: Kobieta
|
||||
Male: Mężczyzna
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Warunki i zasady
|
||||
TosConfirm: Akceptuję
|
||||
TosLinkText: Warunki korzystania
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autoryzacja urządzenia
|
||||
|
@@ -262,6 +262,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Gênero
|
||||
Female: Feminino
|
||||
Male: Masculino
|
||||
@@ -306,6 +307,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Termos e condições
|
||||
TosConfirm: Eu aceito os
|
||||
TosLinkText: termos de serviço
|
||||
@@ -384,6 +386,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorização de dispositivo
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Gen
|
||||
Female: Femeie
|
||||
Male: Bărbat
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Termeni și condiții
|
||||
TosConfirm: Accept
|
||||
TosLinkText: TOS
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Autorizare dispozitiv
|
||||
UserCode:
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Пол
|
||||
Female: Женский
|
||||
Male: Мужской
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Условия использования
|
||||
TosConfirm: Я согласен с
|
||||
TosLinkText: Пользовательским соглашением
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
|
||||
DeviceAuth:
|
||||
Title: Авторизация устройства
|
||||
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Kön
|
||||
Female: Man
|
||||
Male: Kvinna
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Användarvillkor
|
||||
TosConfirm: Jag accepterar
|
||||
TosLinkText: Användarvillkoren
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Tillgång från hårdvaruenhet
|
||||
UserCode:
|
||||
|
531
internal/api/ui/login/static/i18n/tr.yaml
Normal file
531
internal/api/ui/login/static/i18n/tr.yaml
Normal file
@@ -0,0 +1,531 @@
|
||||
Login:
|
||||
Title: Tekrar Hoş Geldiniz!
|
||||
Description: Giriş bilgilerinizi girin.
|
||||
TitleLinking: Kullanıcı bağlama için giriş
|
||||
DescriptionLinking: Harici kullanıcınızı bağlamak için giriş bilgilerinizi girin.
|
||||
LoginNameLabel: Giriş Adı
|
||||
UsernamePlaceHolder: kullanıcıadı
|
||||
LoginnamePlaceHolder: kullanıcıadı@domain
|
||||
ExternalUserDescription: Harici kullanıcı ile giriş yapın.
|
||||
MustBeMemberOfOrg: Kullanıcı {{.OrgName}} organizasyonunun üyesi olmalıdır.
|
||||
RegisterButtonText: Kayıt Ol
|
||||
NextButtonText: İleri
|
||||
|
||||
LDAP:
|
||||
Title: Giriş
|
||||
Description: Giriş bilgilerinizi girin.
|
||||
LoginNameLabel: Giriş Adı
|
||||
PasswordLabel: Şifre
|
||||
NextButtonText: İleri
|
||||
|
||||
SelectAccount:
|
||||
Title: Hesap Seç
|
||||
Description: Hesabınızı kullanın
|
||||
TitleLinking: Kullanıcı bağlama için hesap seç
|
||||
DescriptionLinking: Harici kullanıcınızla bağlamak için hesabınızı seçin.
|
||||
OtherUser: Diğer Kullanıcı
|
||||
SessionState0: aktif
|
||||
SessionState1: Çıkış yapıldı
|
||||
MustBeMemberOfOrg: Kullanıcı {{.OrgName}} organizasyonunun üyesi olmalıdır.
|
||||
|
||||
Password:
|
||||
Title: Şifre
|
||||
Description: Giriş bilgilerinizi girin.
|
||||
PasswordLabel: Şifre
|
||||
MinLength: En az
|
||||
MinLengthp2: karakter uzunluğunda olmalıdır.
|
||||
MaxLength: 70 karakterden az olmalıdır.
|
||||
HasUppercase: Büyük harf içermelidir.
|
||||
HasLowercase: Küçük harf içermelidir.
|
||||
HasNumber: Rakam içermelidir.
|
||||
HasSymbol: Sembol içermelidir.
|
||||
Confirmation: Şifre onayı eşleşti.
|
||||
ResetLinkText: Şifreyi Sıfırla
|
||||
BackButtonText: Geri
|
||||
NextButtonText: İleri
|
||||
|
||||
UsernameChange:
|
||||
Title: Kullanıcı Adını Değiştir
|
||||
Description: Yeni kullanıcı adınızı belirleyin
|
||||
UsernameLabel: Kullanıcı Adı
|
||||
CancelButtonText: İptal
|
||||
NextButtonText: İleri
|
||||
|
||||
UsernameChangeDone:
|
||||
Title: Kullanıcı Adı Değiştirildi
|
||||
Description: Kullanıcı adınız başarıyla değiştirildi.
|
||||
NextButtonText: İleri
|
||||
|
||||
InitPassword:
|
||||
Title: Şifre Belirle
|
||||
Description: Yeni şifrenizi belirlemek için aşağıdaki forma girmeniz gereken bir kod aldınız.
|
||||
CodeLabel: Kod
|
||||
NewPasswordLabel: Yeni Şifre
|
||||
NewPasswordConfirmLabel: Şifreyi Onayla
|
||||
ResendButtonText: Kodu Tekrar Gönder
|
||||
NextButtonText: İleri
|
||||
|
||||
InitPasswordDone:
|
||||
Title: Şifre Belirlendi
|
||||
Description: Şifre başarıyla belirlendi
|
||||
NextButtonText: İleri
|
||||
CancelButtonText: İptal
|
||||
|
||||
InitUser:
|
||||
Title: Kullanıcıyı Etkinleştir
|
||||
Description: E-postanızı aşağıdaki kod ile doğrulayın ve şifrenizi belirleyin.
|
||||
CodeLabel: Kod
|
||||
NewPasswordLabel: Yeni Şifre
|
||||
NewPasswordConfirm: Şifreyi Onayla
|
||||
NextButtonText: İleri
|
||||
ResendButtonText: Kodu Tekrar Gönder
|
||||
|
||||
InitUserDone:
|
||||
Title: Kullanıcı Etkinleştirildi
|
||||
Description: E-posta doğrulandı ve şifre başarıyla belirlendi
|
||||
NextButtonText: İleri
|
||||
CancelButtonText: İptal
|
||||
|
||||
InviteUser:
|
||||
Title: Kullanıcıyı Etkinleştir
|
||||
Description: E-postanızı aşağıdaki kod ile doğrulayın ve şifrenizi belirleyin.
|
||||
CodeLabel: Kod
|
||||
NewPasswordLabel: Yeni Şifre
|
||||
NewPasswordConfirm: Şifreyi Onayla
|
||||
NextButtonText: İleri
|
||||
ResendButtonText: Kodu Tekrar Gönder
|
||||
|
||||
InitMFAPrompt:
|
||||
Title: 2-Faktör Kurulumu
|
||||
Description: 2-faktörlü kimlik doğrulama, kullanıcı hesabınız için ek güvenlik sağlar. Bu sayede hesabınıza yalnızca sizin erişiminiz olması sağlanır.
|
||||
Provider0: Kimlik Doğrulayıcı Uygulama (örn. Google/Microsoft Authenticator, Authy)
|
||||
Provider1: Cihaza bağımlı (örn. FaceID, Windows Hello, Parmak izi)
|
||||
Provider3: OTP SMS
|
||||
Provider4: OTP E-posta
|
||||
NextButtonText: İleri
|
||||
SkipButtonText: Atla
|
||||
|
||||
InitMFAOTP:
|
||||
Title: 2-Faktör Doğrulama
|
||||
Description: 2-faktörünüzü oluşturun. Henüz yoksa bir kimlik doğrulayıcı uygulama indirin.
|
||||
OTPDescription: Kodu kimlik doğrulayıcı uygulamanızla (örn. Google/Microsoft Authenticator, Authy) tarayın veya gizli anahtarı kopyalayın ve aşağıda oluşturulan kodu girin.
|
||||
SecretLabel: Gizli Anahtar
|
||||
CodeLabel: Kod
|
||||
NextButtonText: İleri
|
||||
CancelButtonText: İptal
|
||||
|
||||
InitMFAOTPSMS:
|
||||
Title: 2-Faktör Doğrulama
|
||||
DescriptionPhone: 2-faktörünüzü oluşturun. Doğrulamak için telefon numaranızı girin.
|
||||
DescriptionCode: 2-faktörünüzü oluşturun. Telefon numaranızı doğrulamak için aldığınız kodu girin.
|
||||
PhoneLabel: Telefon
|
||||
CodeLabel: Kod
|
||||
EditButtonText: Düzenle
|
||||
ResendButtonText: Kodu Tekrar Gönder
|
||||
NextButtonText: İleri
|
||||
|
||||
InitMFAU2F:
|
||||
Title: Güvenlik Anahtarı Ekle
|
||||
Description: Güvenlik anahtarı, telefonunuza yerleştirilmiş, Bluetooth kullanan veya bilgisayarınızın USB portuna doğrudan takılan bir doğrulama yöntemidir.
|
||||
TokenNameLabel: Güvenlik anahtarı / cihaz adı
|
||||
NotSupported: WebAuthN tarayıcınız tarafından desteklenmiyor. Lütfen güncel olduğundan emin olun veya farklı bir tarayıcı kullanın (örn. Chrome, Safari, Firefox)
|
||||
RegisterTokenButtonText: Güvenlik anahtarı ekle
|
||||
ErrorRetry: Tekrar deneyin, yeni bir challenge oluşturun veya farklı bir yöntem seçin.
|
||||
|
||||
InitMFADone:
|
||||
Title: 2-faktör Doğrulandı
|
||||
Description: Harika! 2-faktörünüzü başarıyla kurdunuz ve hesabınızı çok daha güvenli hale getirdiniz. Faktör her girişte girilmek zorundadır.
|
||||
NextButtonText: İleri
|
||||
CancelButtonText: İptal
|
||||
|
||||
MFAProvider:
|
||||
Provider0: Kimlik Doğrulayıcı Uygulama (örn. Google/Microsoft Authenticator, Authy)
|
||||
Provider1: Cihaza bağımlı (örn. FaceID, Windows Hello, Parmak izi)
|
||||
Provider3: OTP SMS
|
||||
Provider4: OTP E-posta
|
||||
ChooseOther: veya başka bir seçenek seçin
|
||||
|
||||
VerifyMFAOTP:
|
||||
Title: 2-Faktör Doğrula
|
||||
Description: İkinci faktörünüzü doğrulayın
|
||||
CodeLabel: Kod
|
||||
NextButtonText: İleri
|
||||
|
||||
VerifyOTP:
|
||||
Title: 2-Faktör Doğrula
|
||||
Description: İkinci faktörünüzü doğrulayın
|
||||
CodeLabel: Kod
|
||||
ResendButtonText: Kodu Tekrar Gönder
|
||||
NextButtonText: İleri
|
||||
|
||||
VerifyMFAU2F:
|
||||
Title: 2-Faktör Doğrulama
|
||||
Description: Kayıtlı cihazınızla (örn. FaceID, Windows Hello, Parmak izi) 2-Faktörünüzü doğrulayın
|
||||
NotSupported: WebAuthN tarayıcınız tarafından desteklenmiyor. En yeni sürümü kullandığınızdan emin olun veya desteklenen bir tarayıcıya geçin (Chrome, Safari, Firefox)
|
||||
ErrorRetry: Tekrar deneyin, yeni bir istek oluşturun veya başka bir yöntem seçin.
|
||||
ValidateTokenButtonText: 2-Faktör Doğrula
|
||||
|
||||
Passwordless:
|
||||
Title: Şifresiz Giriş
|
||||
Description: FaceID, Windows Hello veya Parmak izi gibi cihazınızın sağladığı kimlik doğrulama yöntemleriyle giriş yapın.
|
||||
NotSupported: WebAuthN tarayıcınız tarafından desteklenmiyor. Lütfen güncel olduğundan emin olun veya farklı bir tarayıcı kullanın (örn. Chrome, Safari, Firefox)
|
||||
ErrorRetry: Tekrar deneyin, yeni bir challenge oluşturun veya farklı bir yöntem seçin.
|
||||
LoginWithPwButtonText: Şifre ile giriş yap
|
||||
ValidateTokenButtonText: Şifresiz giriş yap
|
||||
|
||||
PasswordlessPrompt:
|
||||
Title: Şifresiz Kurulum
|
||||
Description: Şifresiz giriş kurmak ister misiniz? (FaceID, Windows Hello veya Parmak izi gibi cihazınızın kimlik doğrulama yöntemleri)
|
||||
DescriptionInit: Şifresiz giriş kurmanız gerekiyor. Cihazınızı kaydetmek için size verilen bağlantıyı kullanın.
|
||||
PasswordlessButtonText: Şifresiz devam et
|
||||
NextButtonText: İleri
|
||||
SkipButtonText: Atla
|
||||
|
||||
PasswordlessRegistration:
|
||||
Title: Şifresiz Kurulum
|
||||
Description: Bir isim vererek (örn. BenimTelefonum, MacBook, vb.) ve ardından aşağıdaki 'Şifresiz kaydet' düğmesine tıklayarak kimlik doğrulamanızı ekleyin.
|
||||
TokenNameLabel: Cihazın adı
|
||||
NotSupported: WebAuthN tarayıcınız tarafından desteklenmiyor. Lütfen güncel olduğundan emin olun veya farklı bir tarayıcı kullanın (örn. Chrome, Safari, Firefox)
|
||||
RegisterTokenButtonText: Şifresiz kaydet
|
||||
ErrorRetry: Tekrar deneyin, yeni bir challenge oluşturun veya farklı bir yöntem seçin.
|
||||
|
||||
PasswordlessRegistrationDone:
|
||||
Title: Şifresiz Kurulum Tamamlandı
|
||||
Description: Şifresiz cihaz başarıyla eklendi.
|
||||
DescriptionClose: Artık bu pencereyi kapatabilirsiniz.
|
||||
NextButtonText: İleri
|
||||
CancelButtonText: İptal
|
||||
|
||||
PasswordChange:
|
||||
Title: Şifre Değiştir
|
||||
Description: Şifrenizi değiştirin. Eski ve yeni şifrenizi girin.
|
||||
ExpiredDescription: Şifrenizin süresi dolmuş ve değiştirilmesi gerekiyor. Eski ve yeni şifrenizi girin.
|
||||
OldPasswordLabel: Eski Şifre
|
||||
NewPasswordLabel: Yeni Şifre
|
||||
NewPasswordConfirmLabel: Şifre onayı
|
||||
CancelButtonText: İptal
|
||||
NextButtonText: İleri
|
||||
Footer: Alt Bilgi
|
||||
|
||||
PasswordChangeDone:
|
||||
Title: Şifre Değiştir
|
||||
Description: Şifreniz başarıyla değiştirildi.
|
||||
NextButtonText: İleri
|
||||
|
||||
PasswordResetDone:
|
||||
Title: Şifre Sıfırlama Bağlantısı Gönderildi
|
||||
Description: Şifrenizi sıfırlamak için e-postanızı kontrol edin.
|
||||
NextButtonText: İleri
|
||||
|
||||
EmailVerification:
|
||||
Title: E-Posta Doğrulama
|
||||
Description: Adresinizi doğrulamak için size bir e-posta gönderdik. Lütfen aşağıdaki forma kodu girin.
|
||||
CodeLabel: Kod
|
||||
NextButtonText: İleri
|
||||
ResendButtonText: Kodu Tekrar Gönder
|
||||
|
||||
EmailVerificationDone:
|
||||
Title: E-Posta Doğrulama
|
||||
Description: E-posta adresiniz başarıyla doğrulandı.
|
||||
NextButtonText: İleri
|
||||
CancelButtonText: Cancel
|
||||
LoginButtonText: Login
|
||||
|
||||
RegisterOption:
|
||||
Title: Kayıt Seçenekleri
|
||||
Description: Nasıl kayıt olmak istediğinizi seçin
|
||||
RegisterUsernamePasswordButtonText: Kullanıcı adı ve şifre ile
|
||||
ExternalLoginDescription: veya harici bir kullanıcı ile kayıt olun
|
||||
LoginButtonText: Giriş
|
||||
|
||||
RegistrationUser:
|
||||
Title: Kayıt
|
||||
Description: Kullanıcı verilerinizi girin. E-posta adresiniz giriş adınız olarak kullanılacaktır.
|
||||
DescriptionOrgRegister: Kullanıcı verilerinizi girin.
|
||||
EmailLabel: E-Posta
|
||||
UsernameLabel: Kullanıcı adı
|
||||
FirstnameLabel: Ad
|
||||
LastnameLabel: Soyadı
|
||||
LanguageLabel: Dil
|
||||
German: Deutsch
|
||||
English: English
|
||||
Italian: Italiano
|
||||
French: Français
|
||||
Chinese: 简体中文
|
||||
Polish: Polski
|
||||
Japanese: 日本語
|
||||
Spanish: Español
|
||||
Bulgarian: Български
|
||||
Portuguese: Português
|
||||
Macedonian: Македонски
|
||||
Czech: Čeština
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: Cinsiyet
|
||||
Female: Kadın
|
||||
Male: Erkek
|
||||
Diverse: Çeşitli / X
|
||||
PasswordLabel: Şifre
|
||||
PasswordConfirmLabel: Şifre onayı
|
||||
TosAndPrivacyLabel: Şartlar ve koşullar
|
||||
TosConfirm: Kabul ediyorum
|
||||
TosLinkText: Kullanım Şartları
|
||||
PrivacyConfirm: Kabul ediyorum
|
||||
PrivacyLinkText: gizlilik politikası
|
||||
ExternalLogin: veya harici bir kullanıcı ile kayıt olun
|
||||
BackButtonText: Giriş
|
||||
NextButtonText: İleri
|
||||
|
||||
ExternalRegistrationUserOverview:
|
||||
Title: Harici Kullanıcı Kaydı
|
||||
Description: Seçilen sağlayıcıdan kullanıcı bilgilerinizi aldık. Artık bunları değiştirebilir veya tamamlayabilirsiniz.
|
||||
EmailLabel: E-Posta
|
||||
UsernameLabel: Kullanıcı adı
|
||||
FirstnameLabel: Ad
|
||||
LastnameLabel: Soyadı
|
||||
NicknameLabel: Takma ad
|
||||
PhoneLabel: Telefon numarası
|
||||
LanguageLabel: Dil
|
||||
German: Deutsch
|
||||
English: English
|
||||
Italian: Italiano
|
||||
French: Français
|
||||
Chinese: 简体中文
|
||||
Polish: Polski
|
||||
Japanese: 日本語
|
||||
Spanish: Español
|
||||
Bulgarian: Български
|
||||
Portuguese: Português
|
||||
Macedonian: Македонски
|
||||
Czech: Čeština
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: Şartlar ve koşullar
|
||||
TosConfirm: Kabul ediyorum
|
||||
TosLinkText: Kullanım Şartları
|
||||
PrivacyConfirm: Kabul ediyorum
|
||||
PrivacyLinkText: gizlilik politikası
|
||||
ExternalLogin: veya harici bir kullanıcı ile kayıt olun
|
||||
BackButtonText: Geri
|
||||
NextButtonText: Kaydet
|
||||
|
||||
RegistrationOrg:
|
||||
Title: Organizasyon Kaydı
|
||||
Description: Organizasyon adınızı ve kullanıcı verilerinizi girin.
|
||||
OrgNameLabel: Organizasyon adı
|
||||
EmailLabel: E-Posta
|
||||
UsernameLabel: Kullanıcı adı
|
||||
FirstnameLabel: Ad
|
||||
LastnameLabel: Soyadı
|
||||
PasswordLabel: Şifre
|
||||
PasswordConfirmLabel: Şifre onayı
|
||||
TosAndPrivacyLabel: Şartlar ve koşullar
|
||||
TosConfirm: Kabul ediyorum
|
||||
TosLinkText: Kullanım Şartları
|
||||
PrivacyConfirm: Kabul ediyorum
|
||||
PrivacyLinkText: gizlilik politikası
|
||||
SaveButtonText: Organizasyon oluştur
|
||||
|
||||
LoginSuccess:
|
||||
Title: Giriş Başarılı
|
||||
AutoRedirectDescription: Uygulamanıza otomatik olarak yönlendirileceksiniz. Değilse, aşağıdaki düğmeye tıklayın. Daha sonra pencereyi kapatabilirsiniz.
|
||||
RedirectedDescription: Artık bu pencereyi kapatabilirsiniz.
|
||||
NextButtonText: İleri
|
||||
|
||||
LogoutDone:
|
||||
Title: Çıkış Yapıldı
|
||||
Description: Başarıyla çıkış yaptınız.
|
||||
LoginButtonText: Giriş
|
||||
|
||||
LinkingUserPrompt:
|
||||
Title: Mevcut Kullanıcı Bulundu
|
||||
Description: "Mevcut hesabınızı bağlamak ister misiniz:"
|
||||
LinkButtonText: Bağla
|
||||
OtherButtonText: Diğer seçenekler
|
||||
|
||||
LinkingUsersDone:
|
||||
Title: Kullanıcı Bağlama
|
||||
Description: Kullanıcı bağlandı.
|
||||
CancelButtonText: İptal
|
||||
NextButtonText: İleri
|
||||
|
||||
ExternalNotFound:
|
||||
Title: Harici Kullanıcı Bulunamadı
|
||||
Description: Harici kullanıcı bulunamadı. Kullanıcınızı bağlamak mı yoksa yeni bir kullanıcı otomatik kaydetmek mi istiyorsunuz?
|
||||
LinkButtonText: Bağla
|
||||
AutoRegisterButtonText: Kayıt Ol
|
||||
TosAndPrivacyLabel: Şartlar ve koşullar
|
||||
TosConfirm: Kabul ediyorum
|
||||
TosLinkText: Kullanım Şartları
|
||||
PrivacyConfirm: Kabul ediyorum
|
||||
PrivacyLinkText: gizlilik politikası
|
||||
German: Deutsch
|
||||
English: English
|
||||
Italian: Italiano
|
||||
French: Français
|
||||
Chinese: 简体中文
|
||||
Polish: Polski
|
||||
Japanese: 日本語
|
||||
Spanish: Español
|
||||
Bulgarian: Български
|
||||
Portuguese: Português
|
||||
Macedonian: Македонски
|
||||
Czech: Čeština
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: Cihaz Yetkilendirme
|
||||
UserCode:
|
||||
Label: Kullanıcı Kodu
|
||||
Description: Cihazda sunulan kullanıcı kodunu girin.
|
||||
ButtonNext: İleri
|
||||
Action:
|
||||
Description: Cihaz erişimi verin.
|
||||
GrantDevice: cihaza yetki vermek üzeresiniz
|
||||
AccessToScopes: aşağıdaki kapsam alanlarına erişim
|
||||
Button:
|
||||
Allow: İzin Ver
|
||||
Deny: Reddet
|
||||
Done:
|
||||
Description: Tamamlandı.
|
||||
Approved: Cihaz yetkilendirmesi onaylandı. Artık cihaza dönebilirsiniz.
|
||||
Denied: Cihaz yetkilendirmesi reddedildi. Artık cihaza dönebilirsiniz.
|
||||
|
||||
Footer:
|
||||
PoweredBy: Teknoloji Desteği
|
||||
Tos: Kullanım Şartları
|
||||
PrivacyPolicy: Gizlilik politikası
|
||||
Help: Yardım
|
||||
SupportEmail: Destek E-posta
|
||||
|
||||
SignIn: "{{.Provider}} ile giriş yap"
|
||||
|
||||
Errors:
|
||||
Internal: Dahili bir hata oluştu
|
||||
AuthRequest:
|
||||
NotFound: Kimlik doğrulama isteği bulunamadı
|
||||
UserAgentNotCorresponding: User Agent uyuşmuyor
|
||||
UserAgentNotFound: User Agent ID bulunamadı
|
||||
TokenNotFound: Token bulunamadı
|
||||
RequestTypeNotSupported: İstek türü desteklenmiyor
|
||||
MissingParameters: Gerekli parametreler eksik
|
||||
User:
|
||||
NotFound: Kullanıcı bulunamadı
|
||||
AlreadyExists: Kullanıcı zaten mevcut
|
||||
Inactive: Kullanıcı aktif değil
|
||||
NotFoundOnOrg: Kullanıcı seçilen organizasyonda bulunamadı
|
||||
NotAllowedOrg: Kullanıcı gerekli organizasyonun üyesi değil
|
||||
NotMatchingUserID: Kullanıcı ve kimlik doğrulama isteğindeki kullanıcı uyuşmuyor
|
||||
UserIDMissing: Kullanıcı ID'si boş
|
||||
Invalid: Geçersiz kullanıcı verisi
|
||||
DomainNotAllowedAsUsername: Alan adı zaten rezerve edilmiş ve kullanılamaz
|
||||
NotAllowedToLink: Kullanıcının harici giriş sağlayıcısı ile bağlantı kurma izni yok
|
||||
Profile:
|
||||
NotFound: Profil bulunamadı
|
||||
NotChanged: Profil değişmedi
|
||||
Empty: Profil boş
|
||||
FirstNameEmpty: Profildeki ad boş
|
||||
LastNameEmpty: Profildeki soyadı boş
|
||||
IDMissing: Profil ID'si eksik
|
||||
Email:
|
||||
NotFound: E-posta bulunamadı
|
||||
Invalid: E-posta geçersiz
|
||||
AlreadyVerified: E-posta zaten doğrulanmış
|
||||
NotChanged: E-posta değişmedi
|
||||
Empty: E-posta boş
|
||||
IDMissing: E-posta ID'si eksik
|
||||
Phone:
|
||||
NotFound: Telefon bulunamadı
|
||||
Invalid: Telefon geçersiz
|
||||
AlreadyVerified: Telefon zaten doğrulanmış
|
||||
Empty: Telefon boş
|
||||
NotChanged: Telefon değişmedi
|
||||
Address:
|
||||
NotFound: Adres bulunamadı
|
||||
NotChanged: Adres değişmedi
|
||||
Username:
|
||||
AlreadyExists: Kullanıcı adı zaten alınmış
|
||||
Reserved: Kullanıcı adı zaten alınmış
|
||||
Empty: Kullanıcı adı boş
|
||||
Password:
|
||||
ConfirmationWrong: Şifre onayı yanlış
|
||||
Empty: Şifre boş
|
||||
Invalid: Şifre geçersiz
|
||||
InvalidAndLocked: Şifre geçersiz ve kullanıcı kilitli, yöneticinize başvurun.
|
||||
NotChanged: Yeni şifre mevcut şifrenizle aynı olamaz
|
||||
UsernameOrPassword:
|
||||
Invalid: Kullanıcı adı veya Şifre geçersiz
|
||||
PasswordComplexityPolicy:
|
||||
NotFound: Şifre politikası bulunamadı
|
||||
MinLength: Şifre çok kısa
|
||||
HasLower: Şifre küçük harf içermeli
|
||||
HasUpper: Şifre büyük harf içermeli
|
||||
HasNumber: Şifre sayı içermeli
|
||||
HasSymbol: Şifre sembol içermeli
|
||||
Code:
|
||||
Expired: Kod süresi doldu
|
||||
Invalid: Kod geçersiz
|
||||
Empty: Kod boş
|
||||
CryptoCodeNil: Kripto kodu boş
|
||||
NotFound: Kod bulunamadı
|
||||
GeneratorAlgNotSupported: Desteklenmeyen oluşturucu algoritması
|
||||
EmailVerify:
|
||||
UserIDEmpty: Kullanıcı ID'si boş
|
||||
ExternalData:
|
||||
CouldNotRead: Harici veri doğru okunamadı
|
||||
MFA:
|
||||
NoProviders: Kullanılabilir çok faktörlü kimlik doğrulama sağlayıcısı yok
|
||||
OTP:
|
||||
AlreadyReady: Çok faktörlü OTP (Tek Seferlik Şifre) zaten ayarlanmış
|
||||
NotExisting: Çok faktörlü OTP (Tek Seferlik Şifre) mevcut değil
|
||||
InvalidCode: Geçersiz kod
|
||||
NotReady: Çok faktörlü OTP (Tek Seferlik Şifre) hazır değil
|
||||
Locked: Kullanıcı kilitli
|
||||
SomethingWentWrong: Bir şeyler yanlış gitti
|
||||
NotActive: Kullanıcı aktif değil
|
||||
ExternalIDP:
|
||||
IDPTypeNotImplemented: IDP Türü uygulanmamış
|
||||
NotAllowed: Harici Giriş Sağlayıcısına izin verilmiyor
|
||||
IDPConfigIDEmpty: Kimlik Sağlayıcı ID'si boş
|
||||
ExternalUserIDEmpty: Harici Kullanıcı ID'si boş
|
||||
UserDisplayNameEmpty: Kullanıcı Görünen Adı boş
|
||||
NoExternalUserData: Harici Kullanıcı Verisi alınmadı
|
||||
CreationNotAllowed: Bu sağlayıcıda yeni kullanıcı oluşturmaya izin verilmiyor
|
||||
LinkingNotAllowed: Bu sağlayıcıda kullanıcı bağlamaya izin verilmiyor
|
||||
NoOptionAllowed: Bu sağlayıcıda ne oluşturmaya ne de bağlamaya izin verilmiyor. Lütfen yöneticinizle iletişime geçin.
|
||||
LoginFailedSwitchLocal: |
|
||||
Harici IDP'de giriş başarısız oldu. Yerel girişe geri dönülüyor.
|
||||
|
||||
Hata detayları: {{.Details}}
|
||||
GrantRequired: Giriş mümkün değil. Kullanıcının uygulamada en az bir yetkisi olması gerekiyor. Lütfen yöneticinizle iletişime geçin.
|
||||
ProjectRequired: Giriş mümkün değil. Kullanıcının organizasyonuna proje için yetki verilmiş olması gerekiyor. Lütfen yöneticinizle iletişime geçin.
|
||||
IdentityProvider:
|
||||
InvalidConfig: Kimlik Sağlayıcı yapılandırması geçersiz
|
||||
IAM:
|
||||
LockoutPolicy:
|
||||
NotExisting: Kilitleme Politikası mevcut değil
|
||||
Org:
|
||||
LoginPolicy:
|
||||
RegistrationNotAllowed: Kayıt olmasına izin verilmiyor
|
||||
DeviceAuth:
|
||||
NotExisting: Kullanıcı Kodu mevcut değil
|
||||
|
||||
optional: (isteğe bağlı)
|
@@ -266,6 +266,7 @@ RegistrationUser:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
GenderLabel: 性别
|
||||
Female: 女性
|
||||
Male: 男性
|
||||
@@ -310,6 +311,7 @@ ExternalRegistrationUserOverview:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
TosAndPrivacyLabel: 条款和条款
|
||||
TosConfirm: 我接受
|
||||
TosLinkText: 服务条款
|
||||
@@ -388,6 +390,7 @@ ExternalNotFound:
|
||||
Hungarian: Magyar
|
||||
Korean: 한국어
|
||||
Romanian: Română
|
||||
Turkish: Türkçe
|
||||
DeviceAuth:
|
||||
Title: 设备授权
|
||||
UserCode:
|
||||
|
@@ -102,6 +102,8 @@
|
||||
</option>
|
||||
<option value="ro" id="ro" {{if (selectedLanguage "ro")}} selected {{end}}>{{t "ExternalNotFound.Romanian"}}
|
||||
</option>
|
||||
<option value="tr" id="tr" {{if (selectedLanguage "tr")}} selected {{end}}>{{t "ExternalNotFound.Turkish"}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user