mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 07:37:31 +00:00
feat: restrict login to specific org by id (scope) (#4294)
* feat: add new org scope * change default of UserLoginMustBeDomain to false * return resource owner claims * fix: use email style for first user * fix: ensure email style for default users (backwards compatibility) * change to external domain (as it was before UserLoginMustBeDomain change) * update e2e tests to use email style usernames * document new scope * lint e2e Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
This commit is contained in:
@@ -41,7 +41,7 @@ func (s *Server) GetInstance(ctx context.Context, req *system_pb.GetInstanceRequ
|
||||
}
|
||||
|
||||
func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequest) (*system_pb.AddInstanceResponse, error) {
|
||||
id, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.DefaultInstance))
|
||||
id, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.defaultInstance, s.externalDomain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -1,17 +1,20 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
instance_grpc "github.com/zitadel/zitadel/internal/api/grpc/instance"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
instance_pb "github.com/zitadel/zitadel/pkg/grpc/instance"
|
||||
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
)
|
||||
|
||||
func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup) *command.InstanceSetup {
|
||||
func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup, externalDomain string) *command.InstanceSetup {
|
||||
if req.InstanceName != "" {
|
||||
defaultInstance.InstanceName = req.InstanceName
|
||||
defaultInstance.Org.Name = req.InstanceName
|
||||
@@ -40,6 +43,11 @@ func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInst
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if default username is email style or else append @<orgname>.<custom-domain>
|
||||
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
||||
if !defaultInstance.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(defaultInstance.Org.Human.Username, "@") {
|
||||
defaultInstance.Org.Human.Username = defaultInstance.Org.Human.Username + "@" + domain.NewIAMDomainName(defaultInstance.Org.Name, externalDomain)
|
||||
}
|
||||
if req.OwnerUserName != "" {
|
||||
defaultInstance.Org.Human.Username = req.OwnerUserName
|
||||
}
|
||||
|
@@ -25,25 +25,29 @@ type Server struct {
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
administrator repository.AdministratorRepository
|
||||
DefaultInstance command.InstanceSetup
|
||||
defaultInstance command.InstanceSetup
|
||||
externalDomain string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Repository eventsourcing.Config
|
||||
}
|
||||
|
||||
func CreateServer(command *command.Commands,
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
repo repository.Repository,
|
||||
database string,
|
||||
defaultInstance command.InstanceSetup,
|
||||
externalDomain string,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
administrator: repo,
|
||||
database: database,
|
||||
DefaultInstance: defaultInstance,
|
||||
defaultInstance: defaultInstance,
|
||||
externalDomain: externalDomain,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -95,6 +95,13 @@ func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgIDScope) {
|
||||
if strings.TrimPrefix(scope, domain.OrgIDScope) != user.ResourceOwner {
|
||||
scopes[i] = scopes[len(scopes)-1]
|
||||
scopes[len(scopes)-1] = ""
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
@@ -251,6 +258,16 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
|
||||
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
|
||||
userInfo.AppendClaims(domain.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgIDScope) {
|
||||
userInfo.AppendClaims(domain.OrgIDClaim, strings.TrimPrefix(scope, domain.OrgIDScope))
|
||||
resourceOwnerClaims, err := o.assertUserResourceOwner(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for claim, value := range resourceOwnerClaims {
|
||||
userInfo.AppendClaims(claim, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(roles) == 0 || applicationID == "" {
|
||||
@@ -289,9 +306,20 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
|
||||
}
|
||||
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
|
||||
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
|
||||
} else if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
|
||||
claims = appendClaim(claims, domain.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgIDScope) {
|
||||
claims = appendClaim(claims, domain.OrgIDClaim, strings.TrimPrefix(scope, domain.OrgIDScope))
|
||||
resourceOwnerClaims, err := o.assertUserResourceOwner(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for claim, value := range resourceOwnerClaims {
|
||||
claims = appendClaim(claims, claim, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(roles) == 0 || clientID == "" {
|
||||
return claims, nil
|
||||
|
@@ -103,6 +103,9 @@ func (c *Client) IsScopeAllowed(scope string) bool {
|
||||
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgIDScope) {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.ProjectIDScope) {
|
||||
return true
|
||||
}
|
||||
|
@@ -507,7 +507,7 @@ func (l *Login) isDisplayLoginNameSuffix(authReq *domain.AuthRequest) bool {
|
||||
if authReq == nil {
|
||||
return false
|
||||
}
|
||||
if authReq.RequestedOrgID == "" {
|
||||
if authReq.RequestedOrgID == "" || !authReq.RequestedOrgDomain {
|
||||
return false
|
||||
}
|
||||
return authReq.LabelPolicy != nil && !authReq.LabelPolicy.HideLoginNameSuffix
|
||||
|
Reference in New Issue
Block a user