mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 23:27:31 +00:00
Merge branch 'main' into pnpm-turbo-local-gen
This commit is contained in:
8
Makefile
8
Makefile
@@ -189,16 +189,10 @@ login_push: login_ensure_remote
|
|||||||
login_ensure_remote:
|
login_ensure_remote:
|
||||||
@if ! git remote get-url $(LOGIN_REMOTE_NAME) > /dev/null 2>&1; then \
|
@if ! git remote get-url $(LOGIN_REMOTE_NAME) > /dev/null 2>&1; then \
|
||||||
echo "Adding remote $(LOGIN_REMOTE_NAME)"; \
|
echo "Adding remote $(LOGIN_REMOTE_NAME)"; \
|
||||||
git remote add $(LOGIN_REMOTE_NAME) $(LOGIN_REMOTE_URL); \
|
git remote add --fetch $(LOGIN_REMOTE_NAME) $(LOGIN_REMOTE_URL); \
|
||||||
else \
|
else \
|
||||||
echo "Remote $(LOGIN_REMOTE_NAME) already exists."; \
|
echo "Remote $(LOGIN_REMOTE_NAME) already exists."; \
|
||||||
fi
|
fi
|
||||||
@if [ ! -d login ]; then \
|
|
||||||
echo "Adding subtree for 'login' from branch $(LOGIN_REMOTE_BRANCH)"; \
|
|
||||||
git subtree add --prefix=login $(LOGIN_REMOTE_NAME) $(LOGIN_REMOTE_BRANCH); \
|
|
||||||
else \
|
|
||||||
echo "Subtree 'login' already exists."; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
export LOGIN_DIR := ./login/
|
export LOGIN_DIR := ./login/
|
||||||
export LOGIN_BAKE_CLI_ADDITIONAL_ARGS := --set login-*.context=./login/ --file ./docker-bake.hcl
|
export LOGIN_BAKE_CLI_ADDITIONAL_ARGS := --set login-*.context=./login/ --file ./docker-bake.hcl
|
||||||
|
@@ -201,7 +201,7 @@ func (a *API) registerConnectServer(service server.ConnectServer) {
|
|||||||
methodNames[i] = string(methods.Get(i).Name())
|
methodNames[i] = string(methods.Get(i).Name())
|
||||||
}
|
}
|
||||||
a.connectServices[prefix] = methodNames
|
a.connectServices[prefix] = methodNames
|
||||||
a.RegisterHandlerPrefixes(handler, prefix)
|
a.RegisterHandlerPrefixes(http_mw.CORSInterceptor(handler), prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleFunc allows registering a [http.HandlerFunc] on an exact
|
// HandleFunc allows registering a [http.HandlerFunc] on an exact
|
||||||
|
@@ -9,7 +9,6 @@ import (
|
|||||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
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) {
|
func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain string) ([]string, error) {
|
||||||
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+orgDomain, query.TextEndsWithIgnoreCase)
|
return s.query.SearchClaimedUserIDsOfOrgDomain(ctx, orgDomain, "")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@@ -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) {
|
func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain, orgID string) ([]string, error) {
|
||||||
queries := make([]query.SearchQuery, 0, 2)
|
return s.query.SearchClaimedUserIDsOfOrgDomain(ctx, orgDomain, orgID)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ListOrgMetadata(ctx context.Context, req *mgmt_pb.ListOrgMetadataRequest) (*mgmt_pb.ListOrgMetadataResponse, error) {
|
func (s *Server) ListOrgMetadata(ctx context.Context, req *mgmt_pb.ListOrgMetadataRequest) (*mgmt_pb.ListOrgMetadataResponse, error) {
|
||||||
|
@@ -1044,6 +1044,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) {
|
func TestServer_ListOrganizationDomains(t *testing.T) {
|
||||||
domain := gofakeit.URL()
|
domain := gofakeit.URL()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@@ -250,26 +250,5 @@ func createOrganizationRequestAdminToCommand(admin *v2beta_org.CreateOrganizatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain, orgID string) ([]string, error) {
|
func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain, orgID string) ([]string, error) {
|
||||||
queries := make([]query.SearchQuery, 0, 2)
|
return s.query.SearchClaimedUserIDsOfOrgDomain(ctx, orgDomain, orgID)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@@ -10,30 +10,36 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Authorization = "authorization"
|
Authorization = "authorization"
|
||||||
Accept = "accept"
|
Accept = "accept"
|
||||||
AcceptLanguage = "accept-language"
|
AcceptLanguage = "accept-language"
|
||||||
CacheControl = "cache-control"
|
CacheControl = "cache-control"
|
||||||
ContentType = "content-type"
|
ContentType = "content-type"
|
||||||
ContentLength = "content-length"
|
ContentLength = "content-length"
|
||||||
ContentLocation = "content-location"
|
ContentLocation = "content-location"
|
||||||
Expires = "expires"
|
Expires = "expires"
|
||||||
Location = "location"
|
Location = "location"
|
||||||
Origin = "origin"
|
Origin = "origin"
|
||||||
Pragma = "pragma"
|
Pragma = "pragma"
|
||||||
UserAgentHeader = "user-agent"
|
UserAgentHeader = "user-agent"
|
||||||
ForwardedFor = "x-forwarded-for"
|
ForwardedFor = "x-forwarded-for"
|
||||||
ForwardedHost = "x-forwarded-host"
|
ForwardedHost = "x-forwarded-host"
|
||||||
ForwardedProto = "x-forwarded-proto"
|
ForwardedProto = "x-forwarded-proto"
|
||||||
Forwarded = "forwarded"
|
Forwarded = "forwarded"
|
||||||
ZitadelForwarded = "x-zitadel-forwarded"
|
ZitadelForwarded = "x-zitadel-forwarded"
|
||||||
XUserAgent = "x-user-agent"
|
XUserAgent = "x-user-agent"
|
||||||
XGrpcWeb = "x-grpc-web"
|
XGrpcWeb = "x-grpc-web"
|
||||||
XRequestedWith = "x-requested-with"
|
XRequestedWith = "x-requested-with"
|
||||||
XRobotsTag = "x-robots-tag"
|
XRobotsTag = "x-robots-tag"
|
||||||
IfNoneMatch = "If-None-Match"
|
IfNoneMatch = "if-none-match"
|
||||||
LastModified = "Last-Modified"
|
LastModified = "last-modified"
|
||||||
Etag = "Etag"
|
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"
|
ContentSecurityPolicy = "content-security-policy"
|
||||||
XXSSProtection = "x-xss-protection"
|
XXSSProtection = "x-xss-protection"
|
||||||
|
@@ -21,6 +21,9 @@ var (
|
|||||||
http_utils.XUserAgent,
|
http_utils.XUserAgent,
|
||||||
http_utils.XGrpcWeb,
|
http_utils.XGrpcWeb,
|
||||||
http_utils.XRequestedWith,
|
http_utils.XRequestedWith,
|
||||||
|
http_utils.ConnectProtocolVersion,
|
||||||
|
http_utils.ConnectTimeoutMS,
|
||||||
|
http_utils.GRPCTimeout,
|
||||||
},
|
},
|
||||||
AllowedMethods: []string{
|
AllowedMethods: []string{
|
||||||
http.MethodOptions,
|
http.MethodOptions,
|
||||||
@@ -34,6 +37,9 @@ var (
|
|||||||
ExposedHeaders: []string{
|
ExposedHeaders: []string{
|
||||||
http_utils.Location,
|
http_utils.Location,
|
||||||
http_utils.ContentLength,
|
http_utils.ContentLength,
|
||||||
|
http_utils.GrpcStatus,
|
||||||
|
http_utils.GrpcMessage,
|
||||||
|
http_utils.GrpcStatusDetailsBin,
|
||||||
},
|
},
|
||||||
AllowOriginFunc: func(_ string) bool {
|
AllowOriginFunc: func(_ string) bool {
|
||||||
return true
|
return true
|
||||||
|
@@ -523,7 +523,7 @@ func (l *Login) handleExternalUserAuthenticated(
|
|||||||
// The decision, which information will be checked is based on the IdP template option.
|
// 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.
|
// The function returns a boolean whether a user was found or not.
|
||||||
// If single a user was found, it will be automatically linked.
|
// 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)
|
queries := make([]query.SearchQuery, 0, 2)
|
||||||
switch provider.AutoLinking {
|
switch provider.AutoLinking {
|
||||||
case domain.AutoLinkingOptionUnspecified:
|
case domain.AutoLinkingOptionUnspecified:
|
||||||
@@ -532,7 +532,7 @@ func (l *Login) checkAutoLinking(r *http.Request, authReq *domain.AuthRequest, p
|
|||||||
case domain.AutoLinkingOptionUsername:
|
case domain.AutoLinkingOptionUsername:
|
||||||
// if we're checking for usernames there are to options:
|
// 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.
|
// all existing loginnames and directly use that result to either prompt or continue with other idp options.
|
||||||
if authReq.RequestedOrgID == "" {
|
if authReq.RequestedOrgID == "" {
|
||||||
user, err := l.query.GetNotifyUserByLoginName(r.Context(), false, externalUser.PreferredUsername)
|
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
|
return true, nil
|
||||||
}
|
}
|
||||||
// If a specific org has been requested, we'll check the provided username against usernames (of that org).
|
// If a specific org has been requested, we'll check the username (org policy (suffixed or not) is already applied)
|
||||||
usernameQuery, err := query.NewUserUsernameSearchQuery(externalUser.PreferredUsername, query.TextEqualsIgnoreCase)
|
// against usernames (of that org).
|
||||||
|
usernameQuery, err := query.NewUserUsernameSearchQuery(human.Username, query.TextEqualsIgnoreCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, 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)
|
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
|
// let's check if auto-linking is enabled and if the user would be found by the corresponding option
|
||||||
if provider.AutoLinking != domain.AutoLinkingOptionUnspecified {
|
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 {
|
if err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return false
|
return false
|
||||||
|
@@ -178,19 +178,7 @@ func (l *Login) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgName string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+orgDomain, query.TextEndsWithIgnoreCase)
|
return l.query.SearchClaimedUserIDsOfOrgDomain(ctx, orgDomain, "")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setContext(ctx context.Context, resourceOwner string) context.Context {
|
func setContext(ctx context.Context, resourceOwner string) context.Context {
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
@@ -50,8 +51,10 @@ func (c *Commands) sendInviteCode(ctx context.Context, invite *CreateUserInvite,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.AggregateID); err != nil {
|
if wm.AggregateID != authz.GetCtxData(ctx).UserID {
|
||||||
return nil, nil, err
|
if err := c.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !wm.UserState.Exists() {
|
if !wm.UserState.Exists() {
|
||||||
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wgvn4", "Errors.User.NotFound")
|
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wgvn4", "Errors.User.NotFound")
|
||||||
|
@@ -11,6 +11,7 @@ import (
|
|||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/mock/gomock"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
@@ -205,6 +206,64 @@ func TestCommands_CreateInviteCode(t *testing.T) {
|
|||||||
returnCode: gu.Ptr("code"),
|
returnCode: gu.Ptr("code"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"return ok, with same user requests code",
|
||||||
|
fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("userID", "org1").Aggregate,
|
||||||
|
"username", "firstName",
|
||||||
|
"lastName",
|
||||||
|
"nickName",
|
||||||
|
"displayName",
|
||||||
|
language.Afrikaans,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanInviteCodeAddedEvent(authz.SetCtxData(context.Background(), authz.CtxData{UserID: "userID"}),
|
||||||
|
&user.NewAggregate("userID", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("code"),
|
||||||
|
},
|
||||||
|
time.Hour,
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// we do not run checkPermission() because the same user is requesting the code as the user to which the code is intended for
|
||||||
|
checkPermission: nil,
|
||||||
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour),
|
||||||
|
defaultSecretGenerators: &SecretGenerators{},
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.SetCtxData(context.Background(), authz.CtxData{UserID: "userID"}),
|
||||||
|
invite: &CreateUserInvite{
|
||||||
|
UserID: "userID",
|
||||||
|
ReturnCode: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
ID: "userID",
|
||||||
|
},
|
||||||
|
returnCode: gu.Ptr("code"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"with template and application name ok",
|
"with template and application name ok",
|
||||||
fields{
|
fields{
|
||||||
@@ -510,6 +569,77 @@ func TestCommands_ResendInviteCode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"return ok, with same user requests code",
|
||||||
|
fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("userID", "org1").Aggregate,
|
||||||
|
"username", "firstName",
|
||||||
|
"lastName",
|
||||||
|
"nickName",
|
||||||
|
"displayName",
|
||||||
|
language.Afrikaans,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanInviteCodeAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("userID", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("code"),
|
||||||
|
},
|
||||||
|
time.Hour,
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"authRequestID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanInviteCodeAddedEvent(authz.SetCtxData(context.Background(), authz.CtxData{UserID: "userID"}),
|
||||||
|
&user.NewAggregate("userID", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("code"),
|
||||||
|
},
|
||||||
|
time.Hour,
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"authRequestID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// we do not run checkPermission() because the same user is requesting the code as the user to which the code is intended for
|
||||||
|
checkPermission: nil,
|
||||||
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour),
|
||||||
|
defaultSecretGenerators: &SecretGenerators{},
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
// ctx: context.Background(),
|
||||||
|
ctx: authz.SetCtxData(context.Background(), authz.CtxData{UserID: "userID"}),
|
||||||
|
userID: "userID",
|
||||||
|
},
|
||||||
|
want{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
ID: "userID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resend with new auth requestID ok",
|
"resend with new auth requestID ok",
|
||||||
fields{
|
fields{
|
||||||
|
@@ -697,6 +697,35 @@ func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwn
|
|||||||
return isUnique, err
|
return isUnique, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed user_claimed_user_ids.sql
|
||||||
|
var userClaimedUserIDOfOrgDomain string
|
||||||
|
|
||||||
|
func (q *Queries) SearchClaimedUserIDsOfOrgDomain(ctx context.Context, domain, orgID string) (userIDs []string, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
err = q.client.QueryContext(ctx,
|
||||||
|
func(rows *sql.Rows) error {
|
||||||
|
userIDs = make([]string, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var userID string
|
||||||
|
err := rows.Scan(&userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userIDs = append(userIDs, userID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
userClaimedUserIDOfOrgDomain,
|
||||||
|
authz.GetInstance(ctx).InstanceID(),
|
||||||
|
"%@"+domain,
|
||||||
|
orgID,
|
||||||
|
)
|
||||||
|
|
||||||
|
return userIDs, err
|
||||||
|
}
|
||||||
|
|
||||||
func (q *UserSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
func (q *UserSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
query = q.SearchRequest.toQuery(query)
|
query = q.SearchRequest.toQuery(query)
|
||||||
for _, q := range q.Queries {
|
for _, q := range q.Queries {
|
||||||
|
13
internal/query/user_claimed_user_ids.sql
Normal file
13
internal/query/user_claimed_user_ids.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
SELECT u.id
|
||||||
|
FROM projections.login_names3_users u
|
||||||
|
LEFT JOIN projections.login_names3_policies p_custom
|
||||||
|
ON u.instance_id = p_custom.instance_id
|
||||||
|
AND p_custom.instance_id = $1
|
||||||
|
AND p_custom.resource_owner = u.resource_owner
|
||||||
|
JOIN projections.login_names3_policies p_default
|
||||||
|
ON u.instance_id = p_default.instance_id
|
||||||
|
AND p_default.instance_id = $1 AND p_default.is_default IS TRUE
|
||||||
|
WHERE u.instance_id = $1
|
||||||
|
AND COALESCE(p_custom.must_be_domain, p_default.must_be_domain) = false
|
||||||
|
AND u.user_name_lower like $2
|
||||||
|
AND u.resource_owner <> $3;
|
18
login/.github/workflows/close_pr.yml
vendored
18
login/.github/workflows/close_pr.yml
vendored
@@ -1,6 +1,7 @@
|
|||||||
name: Auto-close PRs and guide to correct repo
|
name: Auto-close PRs and guide to correct repo
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened]
|
types: [opened]
|
||||||
|
|
||||||
@@ -14,14 +15,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const message = `
|
const message = `
|
||||||
👋 **Thanks for your contribution!**
|
👋 **Thanks for your contribution @${{ github.event.pull_request.user.login }}!**
|
||||||
|
|
||||||
This repository \`${{ github.repository }}\` is a read-only mirror of our internal development in [\`zitadel/zitadel\`](https://github.com/zitadel/zitadel).
|
This repository \`${{ github.repository }}\` is a read-only mirror of the git subtree at [\`zitadel/zitadel/login\`](https://github.com/zitadel/zitadel).
|
||||||
Therefore, we close this pull request automatically, but submitting your changes to the main repository is easy:
|
Therefore, we close this pull request automatically.
|
||||||
1. Fork and clone zitadel/zitadel
|
|
||||||
2. Create a new branch for your changes
|
Your changes are not lost. Submitting them to the main repository is easy:
|
||||||
3. Pull your changes into the new fork by running `make login_pull LOGIN_REMOTE_URL=<your-typescript-fork-org>/typescript LOGIN_REMOTE_BRANCH=<your-typescript-fork-branch>`.
|
1. [Fork zitadel/zitadel](https://github.com/zitadel/zitadel/fork)
|
||||||
4. Push your changes and open a pull request to zitadel/zitadel
|
2. Clone your Zitadel fork \`git clone https://github.com/<your-owner>/zitadel.git\`
|
||||||
|
3. Change directory to your Zitadel forks root.
|
||||||
|
4. Pull your changes into the Zitadel fork by running \`make login_pull LOGIN_REMOTE_URL=https://github.com/<your-owner>/typescript.git LOGIN_REMOTE_BRANCH=<your-typescript-fork-branch>\`.
|
||||||
|
5. Push your changes and [open a pull request to zitadel/zitadel](https://github.com/zitadel/zitadel/compare)
|
||||||
`.trim();
|
`.trim();
|
||||||
await github.rest.issues.createComment({
|
await github.rest.issues.createComment({
|
||||||
...context.repo,
|
...context.repo,
|
||||||
|
Reference in New Issue
Block a user