feat(actions): local users (#5089)

Actions are extended to to local users. It's possible to run custom code during registration and authentication of local users.
This commit is contained in:
Silvan
2023-01-25 14:08:01 +01:00
committed by GitHub
parent 19621acfd3
commit c54ddc71a2
48 changed files with 704 additions and 188 deletions

View File

@@ -0,0 +1,122 @@
package object
import (
"net"
"time"
"github.com/dop251/goja"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/domain"
)
// AuthRequestField accepts the domain.AuthRequest by value, so its not mutated
func AuthRequestField(authRequest *domain.AuthRequest) func(c *actions.FieldConfig) interface{} {
return func(c *actions.FieldConfig) interface{} {
return AuthRequestFromDomain(c, authRequest)
}
}
func AuthRequestFromDomain(c *actions.FieldConfig, request *domain.AuthRequest) goja.Value {
return c.Runtime.ToValue(&authRequest{
Id: request.ID,
AgentId: request.AgentID,
CreationDate: request.CreationDate,
ChangeDate: request.ChangeDate,
BrowserInfo: &browserInfo{
UserAgent: request.BrowserInfo.UserAgent,
AcceptLanguage: request.BrowserInfo.AcceptLanguage,
RemoteIp: request.BrowserInfo.RemoteIP,
},
ApplicationId: request.ApplicationID,
CallbackUri: request.CallbackURI,
TransferState: request.TransferState,
Prompt: request.Prompt,
UiLocales: request.UiLocales,
LoginHint: request.LoginHint,
MaxAuthAge: request.MaxAuthAge,
InstanceId: request.InstanceID,
Request: requestFromDomain(request.Request),
UserId: request.UserID,
UserName: request.UserName,
LoginName: request.LoginName,
DisplayName: request.DisplayName,
ResourceOwner: request.UserOrgID,
RequestedOrgId: request.RequestedOrgID,
RequestedOrgName: request.RequestedOrgName,
RequestedPrimaryDomain: request.RequestedPrimaryDomain,
RequestedOrgDomain: request.RequestedOrgDomain,
ApplicationResourceOwner: request.ApplicationResourceOwner,
PrivateLabelingSetting: request.PrivateLabelingSetting,
SelectedIdpConfigId: request.SelectedIDPConfigID,
LinkingUsers: externalUsersFromDomain(request.LinkingUsers),
PasswordVerified: request.PasswordVerified,
MfasVerified: request.MFAsVerified,
Audience: request.Audience,
AuthTime: request.AuthTime,
})
}
type authRequest struct {
Id string
AgentId string
CreationDate time.Time
ChangeDate time.Time
BrowserInfo *browserInfo
ApplicationId string
CallbackUri string
TransferState string
Prompt []domain.Prompt
UiLocales []string
LoginHint string
MaxAuthAge *time.Duration
InstanceId string
Request *request
UserId string
UserName string
LoginName string
DisplayName string
// UserOrgID string
ResourceOwner string
// requested by scope
RequestedOrgId string
// requested by scope
RequestedOrgName string
// requested by scope
RequestedPrimaryDomain string
// requested by scope
RequestedOrgDomain bool
// client
ApplicationResourceOwner string
PrivateLabelingSetting domain.PrivateLabelingSetting
SelectedIdpConfigId string
LinkingUsers []*externalUser
PasswordVerified bool
MfasVerified []domain.MFAType
Audience []string
AuthTime time.Time
}
func requestFromDomain(req domain.Request) *request {
r := new(request)
if oidcRequest, ok := req.(*domain.AuthRequestOIDC); ok {
r.Oidc = OIDCRequest{Scopes: oidcRequest.Scopes}
}
return r
}
type request struct {
Oidc OIDCRequest
}
type OIDCRequest struct {
Scopes []string
}
type browserInfo struct {
UserAgent string
AcceptLanguage string
RemoteIp net.IP
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
@@ -54,3 +55,87 @@ type userMetadata struct {
Key string
Value goja.Value
}
type MetadataList struct {
Metadata []*Metadata
}
type Metadata struct {
Key string
// Value is for exporting to javascript
Value goja.Value
// value is for mapping to [domain.Metadata]
value []byte
}
func (md *MetadataList) AppendMetadataFunc(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 2 {
panic("exactly 2 (key, value) arguments expected")
}
value, err := json.Marshal(call.Arguments[1].Export())
if err != nil {
logging.WithError(err).Debug("unable to marshal")
panic(err)
}
md.Metadata = append(md.Metadata,
&Metadata{
Key: call.Arguments[0].Export().(string),
Value: call.Arguments[1],
value: value,
})
return nil
}
func MetadataListToDomain(metadataList *MetadataList) []*domain.Metadata {
if metadataList == nil {
return nil
}
list := make([]*domain.Metadata, len(metadataList.Metadata))
for i, metadata := range metadataList.Metadata {
list[i] = &domain.Metadata{
Key: metadata.Key,
Value: metadata.value,
}
}
return list
}
func MetadataField(metadata *MetadataList) func(c *actions.FieldConfig) interface{} {
return func(c *actions.FieldConfig) interface{} {
for _, md := range metadata.Metadata {
if json.Valid(md.value) {
err := json.Unmarshal(md.value, &md.Value)
if err != nil {
panic(err)
}
}
}
return metadata.Metadata
}
}
func MetadataListFromDomain(metadata []*domain.Metadata) *MetadataList {
list := &MetadataList{Metadata: make([]*Metadata, len(metadata))}
for i, md := range metadata {
var val interface{}
if json.Valid(md.Value) {
err := json.Unmarshal(md.Value, &val)
if err != nil {
panic(err)
}
}
list.Metadata[i] = &Metadata{
Key: md.Key,
value: md.Value,
}
}
return list
}

View File

@@ -0,0 +1,14 @@
package object
import "github.com/dop251/goja"
func objectFromFirstArgument(call goja.FunctionCall, runtime *goja.Runtime) *goja.Object {
if len(call.Arguments) != 1 {
panic("exactly one argument expected")
}
object := call.Arguments[0].ToObject(runtime)
if object == nil {
panic("unable to unmarshal arg")
}
return object
}

View File

@@ -11,7 +11,21 @@ import (
)
func UserFromExternalUser(c *actions.FieldConfig, user *domain.ExternalUser) goja.Value {
return c.Runtime.ToValue(&externalUser{
return c.Runtime.ToValue(externalUserFromDomain(user))
}
func externalUsersFromDomain(users []*domain.ExternalUser) []*externalUser {
externalUsers := make([]*externalUser, len(users))
for i, user := range users {
externalUsers[i] = externalUserFromDomain(user)
}
return externalUsers
}
func externalUserFromDomain(user *domain.ExternalUser) *externalUser {
return &externalUser{
ExternalId: user.ExternalUserID,
ExternalIdpId: user.ExternalUserID,
Human: human{
@@ -25,7 +39,7 @@ func UserFromExternalUser(c *actions.FieldConfig, user *domain.ExternalUser) goj
Phone: user.Phone,
IsPhoneVerified: user.IsPhoneVerified,
},
})
}
}
func UserFromHuman(c *actions.FieldConfig, user *domain.Human) goja.Value {
@@ -95,6 +109,7 @@ func humanFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
},
})
}
func machineFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
return c.Runtime.ToValue(&machineUser{
Id: user.ID,

View File

@@ -0,0 +1,68 @@
package object
import (
"github.com/dop251/goja"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/domain"
)
type UserGrants struct {
UserGrants []UserGrant
}
type UserGrant struct {
ProjectID string
ProjectGrantID string
Roles []string
}
func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value {
return func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value {
return func(call goja.FunctionCall) goja.Value {
firstArg := objectFromFirstArgument(call, c.Runtime)
grant := UserGrant{}
mapObjectToGrant(firstArg, &grant)
userGrants.UserGrants = append(userGrants.UserGrants, grant)
return nil
}
}
}
func mapObjectToGrant(object *goja.Object, grant *UserGrant) {
for _, key := range object.Keys() {
switch key {
case "projectId":
grant.ProjectID = object.Get(key).String()
case "projectGrantId":
grant.ProjectGrantID = object.Get(key).String()
case "roles":
if roles, ok := object.Get(key).Export().([]interface{}); ok {
for _, role := range roles {
if r, ok := role.(string); ok {
grant.Roles = append(grant.Roles, r)
}
}
}
}
}
if grant.ProjectID == "" {
panic("projectId not set")
}
}
func UserGrantsToDomain(userID string, actionUserGrants []UserGrant) []*domain.UserGrant {
if actionUserGrants == nil {
return nil
}
userGrants := make([]*domain.UserGrant, len(actionUserGrants))
for i, grant := range actionUserGrants {
userGrants[i] = &domain.UserGrant{
UserID: userID,
ProjectID: grant.ProjectID,
ProjectGrantID: grant.ProjectGrantID,
RoleKeys: grant.Roles,
}
}
return userGrants
}