Implementation actions & errors for access control, auth, role & user
This commit is contained in:
parent
f84f8b5b33
commit
5f8fb8a294
@ -347,63 +347,77 @@ func {{ camelCase "" $.Service "Err" $e.Error }}(props ... *{{ $.Service }}Actio
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (svc {{ $.Service }}) recordAction(ctx context.Context, props *{{ $.Service }}ActionProps, action func(... *{{ $.Service }}ActionProps) *{{ $.Service }}Action, err error) error {
|
||||
// If non-{{ $.Service }}Error error was passed,
|
||||
// wrap it with generic error
|
||||
var (
|
||||
svcErr *{{ $.Service }}Error
|
||||
ok bool
|
||||
)
|
||||
ok bool
|
||||
|
||||
// Return error
|
||||
retError *{{ $.Service }}Error
|
||||
|
||||
// Recorder error
|
||||
recError *{{ $.Service }}Error
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if svcErr, ok = err.(*{{ $.Service }}Error); !ok {
|
||||
// wrap non-{{ $.Service }} errors
|
||||
svcErr = {{ camelCase "" $.Service "err" "generic" }}(props).Wrap(err)
|
||||
if retError, ok = err.(*{{ $.Service }}Error); !ok {
|
||||
// got non-{{ $.Service }} error, wrap it with {{ camelCase "" $.Service "err" "generic" }}
|
||||
retError = {{ camelCase "" $.Service "err" "generic" }}(props).Wrap(err)
|
||||
|
||||
// make sure we return generic error!
|
||||
err = svcErr
|
||||
} else {
|
||||
// find the original cause for this error
|
||||
// for the purpose of logging
|
||||
//
|
||||
// we'll return the received error as-is
|
||||
// copy action to returning and recording error
|
||||
retError.action = action().action
|
||||
|
||||
// we'll use {{ camelCase "" $.Service "err" "generic" }} for recording too
|
||||
// because it can hold more info
|
||||
recError = retError
|
||||
} else if retError != nil {
|
||||
// copy action to returning and recording error
|
||||
retError.action = action().action
|
||||
// start with copy of return error for recording
|
||||
// this will be updated with tha root cause as we try and
|
||||
// unwrap the error
|
||||
recError = retError
|
||||
|
||||
// find the original recError for this error
|
||||
// for the purpose of logging
|
||||
var unwrappedError error = retError
|
||||
for {
|
||||
ue := errors.Unwrap(svcErr)
|
||||
if ue == nil {
|
||||
// nothing wrapped
|
||||
if unwrappedError = errors.Unwrap(unwrappedError); unwrappedError == nil {
|
||||
// nothing wrapped
|
||||
break
|
||||
}
|
||||
|
||||
if _, ok = ue.(*{{ $.Service }}Error); !ok {
|
||||
// wrapped error is not of type *{{ $.Service }}Error
|
||||
break
|
||||
// update recError ONLY of wrapped error is of type {{ $.Service }}Error
|
||||
if unwrappedSinkError, ok := unwrappedError.(*{{ $.Service }}Error); ok {
|
||||
recError = unwrappedSinkError
|
||||
}
|
||||
|
||||
svcErr = ue.(*{{ $.Service }}Error)
|
||||
}
|
||||
|
||||
if svcErr.props == nil {
|
||||
// got {{ $.Service }}Error w/o props,
|
||||
// assign props from args
|
||||
svcErr.props = props
|
||||
}
|
||||
}
|
||||
if retError.props == nil {
|
||||
// set props on returning error if empty
|
||||
retError.props = props
|
||||
}
|
||||
|
||||
if action != nil {
|
||||
// copy action to error
|
||||
a := action()
|
||||
svcErr.action = a.action
|
||||
if recError.props == nil {
|
||||
// set props on recording error if empty
|
||||
recError.props = props
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if svc.actionlog != nil {
|
||||
if svcErr != nil {
|
||||
// failed action, log error
|
||||
svc.actionlog.Record(ctx, svcErr)
|
||||
if retError != nil {
|
||||
// failed action, log error
|
||||
svc.actionlog.Record(ctx, recError)
|
||||
} else if action != nil {
|
||||
// successful
|
||||
// successful
|
||||
svc.actionlog.Record(ctx, action(props))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
if err == nil {
|
||||
// retError not an interface and that WILL (!!) cause issues
|
||||
// with nil check (== nil) when it is not explicitly returned
|
||||
return nil
|
||||
}
|
||||
|
||||
return retError
|
||||
}
|
||||
|
||||
@ -25,10 +25,10 @@ type (
|
||||
|
||||
const (
|
||||
// Hardcoded Role ID for everyone
|
||||
EveryoneRoleID = 1
|
||||
EveryoneRoleID uint64 = 1
|
||||
|
||||
// Hardcoded ID for Admin role
|
||||
AdminsRoleID = 2
|
||||
AdminsRoleID uint64 = 2
|
||||
)
|
||||
|
||||
func (op Operation) String() string {
|
||||
|
||||
@ -2,6 +2,8 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/permissions"
|
||||
@ -11,6 +13,7 @@ import (
|
||||
type (
|
||||
accessControl struct {
|
||||
permissions accessControlPermissionServicer
|
||||
actionlog actionlog.Recorder
|
||||
}
|
||||
|
||||
accessControlPermissionServicer interface {
|
||||
@ -28,6 +31,7 @@ type (
|
||||
func AccessControl(perm accessControlPermissionServicer) *accessControl {
|
||||
return &accessControl{
|
||||
permissions: perm,
|
||||
actionlog: DefaultActionlog,
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,15 +185,35 @@ func (svc accessControl) can(ctx context.Context, res permissionResource, op per
|
||||
|
||||
func (svc accessControl) Grant(ctx context.Context, rr ...*permissions.Rule) error {
|
||||
if !svc.CanGrant(ctx) {
|
||||
return ErrNoGrantPermissions
|
||||
return AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
return svc.permissions.Grant(ctx, svc.Whitelist(), rr...)
|
||||
if err := svc.permissions.Grant(ctx, svc.Whitelist(), rr...); err != nil {
|
||||
return AccessControlErrGeneric().Wrap(err)
|
||||
}
|
||||
|
||||
svc.logGrants(ctx, rr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc accessControl) logGrants(ctx context.Context, rr []*permissions.Rule) {
|
||||
if svc.actionlog == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
g := AccessControlActionGrant(&accessControlActionProps{r})
|
||||
g.log = r.String()
|
||||
g.resource = r.Resource.String()
|
||||
|
||||
svc.actionlog.Record(ctx, g)
|
||||
}
|
||||
}
|
||||
|
||||
func (svc accessControl) FindRulesByRoleID(ctx context.Context, roleID uint64) (permissions.RuleSet, error) {
|
||||
if !svc.CanGrant(ctx) {
|
||||
return nil, ErrNoPermissions
|
||||
return nil, AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
return svc.permissions.FindRulesByRoleID(roleID), nil
|
||||
|
||||
407
system/service/access_control_actions.gen.go
Normal file
407
system/service/access_control_actions.gen.go
Normal file
@ -0,0 +1,407 @@
|
||||
package service
|
||||
|
||||
// This file is auto-generated from system/service/access_control_actions.yaml
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza-server/pkg/permissions"
|
||||
)
|
||||
|
||||
type (
|
||||
accessControlActionProps struct {
|
||||
rule *permissions.Rule
|
||||
}
|
||||
|
||||
accessControlAction struct {
|
||||
timestamp time.Time
|
||||
resource string
|
||||
action string
|
||||
log string
|
||||
severity actionlog.Severity
|
||||
|
||||
// prefix for error when action fails
|
||||
errorMessage string
|
||||
|
||||
props *accessControlActionProps
|
||||
}
|
||||
|
||||
accessControlError struct {
|
||||
timestamp time.Time
|
||||
error string
|
||||
resource string
|
||||
action string
|
||||
message string
|
||||
log string
|
||||
severity actionlog.Severity
|
||||
|
||||
wrap error
|
||||
|
||||
props *accessControlActionProps
|
||||
}
|
||||
)
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Props methods
|
||||
// setRule updates accessControlActionProps's rule
|
||||
//
|
||||
// Allows method chaining
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (p *accessControlActionProps) setRule(rule *permissions.Rule) *accessControlActionProps {
|
||||
p.rule = rule
|
||||
return p
|
||||
}
|
||||
|
||||
// serialize converts accessControlActionProps to actionlog.Meta
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (p accessControlActionProps) serialize() actionlog.Meta {
|
||||
var (
|
||||
m = make(actionlog.Meta)
|
||||
str = func(i interface{}) string { return fmt.Sprintf("%v", i) }
|
||||
)
|
||||
|
||||
if p.rule != nil {
|
||||
m["rule.operation"] = str(p.rule.Operation)
|
||||
m["rule.roleID"] = str(p.rule.RoleID)
|
||||
m["rule.access"] = str(p.rule.Access)
|
||||
m["rule.resource"] = str(p.rule.Resource)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// tr translates string and replaces meta value placeholder with values
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (p accessControlActionProps) tr(in string, err error) string {
|
||||
var pairs = []string{"{err}"}
|
||||
|
||||
if err != nil {
|
||||
for {
|
||||
// Unwrap errors
|
||||
ue := errors.Unwrap(err)
|
||||
if ue == nil {
|
||||
break
|
||||
}
|
||||
|
||||
err = ue
|
||||
}
|
||||
|
||||
pairs = append(pairs, err.Error())
|
||||
} else {
|
||||
pairs = append(pairs, "nil")
|
||||
}
|
||||
|
||||
if p.rule != nil {
|
||||
pairs = append(pairs, "{rule}", fmt.Sprintf("%v", p.rule.Operation))
|
||||
pairs = append(pairs, "{rule.operation}", fmt.Sprintf("%v", p.rule.Operation))
|
||||
pairs = append(pairs, "{rule.roleID}", fmt.Sprintf("%v", p.rule.RoleID))
|
||||
pairs = append(pairs, "{rule.access}", fmt.Sprintf("%v", p.rule.Access))
|
||||
pairs = append(pairs, "{rule.resource}", fmt.Sprintf("%v", p.rule.Resource))
|
||||
}
|
||||
return strings.NewReplacer(pairs...).Replace(in)
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Action methods
|
||||
|
||||
// String returns loggable description as string
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (a *accessControlAction) String() string {
|
||||
var props = &accessControlActionProps{}
|
||||
|
||||
if a.props != nil {
|
||||
props = a.props
|
||||
}
|
||||
|
||||
return props.tr(a.log, nil)
|
||||
}
|
||||
|
||||
func (e *accessControlAction) LoggableAction() *actionlog.Action {
|
||||
return &actionlog.Action{
|
||||
Timestamp: e.timestamp,
|
||||
Resource: e.resource,
|
||||
Action: e.action,
|
||||
Severity: e.severity,
|
||||
Description: e.String(),
|
||||
Meta: e.props.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Error methods
|
||||
|
||||
// String returns loggable description as string
|
||||
//
|
||||
// It falls back to message if log is not set
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (e *accessControlError) String() string {
|
||||
var props = &accessControlActionProps{}
|
||||
|
||||
if e.props != nil {
|
||||
props = e.props
|
||||
}
|
||||
|
||||
if e.wrap != nil && !strings.Contains(e.log, "{err}") {
|
||||
// Suffix error log with {err} to ensure
|
||||
// we log the cause for this error
|
||||
e.log += ": {err}"
|
||||
}
|
||||
|
||||
return props.tr(e.log, e.wrap)
|
||||
}
|
||||
|
||||
// Error satisfies
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (e *accessControlError) Error() string {
|
||||
var props = &accessControlActionProps{}
|
||||
|
||||
if e.props != nil {
|
||||
props = e.props
|
||||
}
|
||||
|
||||
return props.tr(e.message, e.wrap)
|
||||
}
|
||||
|
||||
// Is fn for error equality check
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (e *accessControlError) Is(Resource error) bool {
|
||||
t, ok := Resource.(*accessControlError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return t.resource == e.resource && t.error == e.error
|
||||
}
|
||||
|
||||
// Wrap wraps accessControlError around another error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (e *accessControlError) Wrap(err error) *accessControlError {
|
||||
e.wrap = err
|
||||
return e
|
||||
}
|
||||
|
||||
// Unwrap returns wrapped error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (e *accessControlError) Unwrap() error {
|
||||
return e.wrap
|
||||
}
|
||||
|
||||
func (e *accessControlError) LoggableAction() *actionlog.Action {
|
||||
return &actionlog.Action{
|
||||
Timestamp: e.timestamp,
|
||||
Resource: e.resource,
|
||||
Action: e.action,
|
||||
Severity: e.severity,
|
||||
Description: e.String(),
|
||||
Error: e.Error(),
|
||||
Meta: e.props.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Action constructors
|
||||
|
||||
// AccessControlActionGrant returns "system:access_control.grant" error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func AccessControlActionGrant(props ...*accessControlActionProps) *accessControlAction {
|
||||
a := &accessControlAction{
|
||||
timestamp: time.Now(),
|
||||
resource: "system:access_control",
|
||||
action: "grant",
|
||||
log: "grant",
|
||||
severity: actionlog.Error,
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
a.props = props[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
// Error constructors
|
||||
|
||||
// AccessControlErrGeneric returns "system:access_control.generic" audit event as actionlog.Error
|
||||
//
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func AccessControlErrGeneric(props ...*accessControlActionProps) *accessControlError {
|
||||
var e = &accessControlError{
|
||||
timestamp: time.Now(),
|
||||
resource: "system:access_control",
|
||||
error: "generic",
|
||||
action: "error",
|
||||
message: "failed to complete request due to internal error",
|
||||
log: "{err}",
|
||||
severity: actionlog.Error,
|
||||
props: func() *accessControlActionProps {
|
||||
if len(props) > 0 {
|
||||
return props[0]
|
||||
}
|
||||
return nil
|
||||
}(),
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
e.props = props[0]
|
||||
}
|
||||
|
||||
return e
|
||||
|
||||
}
|
||||
|
||||
// AccessControlErrNotAllowedToSetPermissions returns "system:access_control.notAllowedToSetPermissions" audit event as actionlog.Alert
|
||||
//
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func AccessControlErrNotAllowedToSetPermissions(props ...*accessControlActionProps) *accessControlError {
|
||||
var e = &accessControlError{
|
||||
timestamp: time.Now(),
|
||||
resource: "system:access_control",
|
||||
error: "notAllowedToSetPermissions",
|
||||
action: "error",
|
||||
message: "not allowed to set permissions",
|
||||
log: "not allowed to set permissions",
|
||||
severity: actionlog.Alert,
|
||||
props: func() *accessControlActionProps {
|
||||
if len(props) > 0 {
|
||||
return props[0]
|
||||
}
|
||||
return nil
|
||||
}(),
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
e.props = props[0]
|
||||
}
|
||||
|
||||
return e
|
||||
|
||||
}
|
||||
|
||||
// *********************************************************************************************************************
|
||||
// *********************************************************************************************************************
|
||||
|
||||
// recordAction is a service helper function wraps function that can return error
|
||||
//
|
||||
// context is used to enrich audit log entry with current user info, request ID, IP address...
|
||||
// props are collected action/error properties
|
||||
// action (optional) fn will be used to construct accessControlAction struct from given props (and error)
|
||||
// err is any error that occurred while action was happening
|
||||
//
|
||||
// Action has success and fail (error) state:
|
||||
// - when recorded without an error (4th param), action is recorded as successful.
|
||||
// - when an additional error is given (4th param), action is used to wrap
|
||||
// the additional error
|
||||
//
|
||||
// This function is auto-generated.
|
||||
//
|
||||
func (svc accessControl) recordAction(ctx context.Context, props *accessControlActionProps, action func(...*accessControlActionProps) *accessControlAction, err error) error {
|
||||
var (
|
||||
ok bool
|
||||
|
||||
// Return error
|
||||
retError *accessControlError
|
||||
|
||||
// Recorder error
|
||||
recError *accessControlError
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if retError, ok = err.(*accessControlError); !ok {
|
||||
// got non-accessControl error, wrap it with AccessControlErrGeneric
|
||||
retError = AccessControlErrGeneric(props).Wrap(err)
|
||||
|
||||
// copy action to returning and recording error
|
||||
retError.action = action().action
|
||||
|
||||
// we'll use AccessControlErrGeneric for recording too
|
||||
// because it can hold more info
|
||||
recError = retError
|
||||
} else if retError != nil {
|
||||
// copy action to returning and recording error
|
||||
retError.action = action().action
|
||||
// start with copy of return error for recording
|
||||
// this will be updated with tha root cause as we try and
|
||||
// unwrap the error
|
||||
recError = retError
|
||||
|
||||
// find the original recError for this error
|
||||
// for the purpose of logging
|
||||
var unwrappedError error = retError
|
||||
for {
|
||||
if unwrappedError = errors.Unwrap(unwrappedError); unwrappedError == nil {
|
||||
// nothing wrapped
|
||||
break
|
||||
}
|
||||
|
||||
// update recError ONLY of wrapped error is of type accessControlError
|
||||
if unwrappedSinkError, ok := unwrappedError.(*accessControlError); ok {
|
||||
recError = unwrappedSinkError
|
||||
}
|
||||
}
|
||||
|
||||
if retError.props == nil {
|
||||
// set props on returning error if empty
|
||||
retError.props = props
|
||||
}
|
||||
|
||||
if recError.props == nil {
|
||||
// set props on recording error if empty
|
||||
recError.props = props
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if svc.actionlog != nil {
|
||||
if retError != nil {
|
||||
// failed action, log error
|
||||
svc.actionlog.Record(ctx, recError)
|
||||
} else if action != nil {
|
||||
// successful
|
||||
svc.actionlog.Record(ctx, action(props))
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// retError not an interface and that WILL (!!) cause issues
|
||||
// with nil check (== nil) when it is not explicitly returned
|
||||
return nil
|
||||
}
|
||||
|
||||
return retError
|
||||
}
|
||||
25
system/service/access_control_actions.yaml
Normal file
25
system/service/access_control_actions.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
# List of security/audit events and errors that we need to log
|
||||
|
||||
resource: system:access_control
|
||||
service: accessControl
|
||||
|
||||
# Default sensitivity for actions
|
||||
defaultActionSeverity: note
|
||||
|
||||
# default severity for errors
|
||||
defaultErrorSeverity: alert
|
||||
|
||||
import:
|
||||
- github.com/cortezaproject/corteza-server/pkg/permissions
|
||||
|
||||
props:
|
||||
- name: rule
|
||||
type: "*permissions.Rule"
|
||||
fields: [ operation, roleID, access, resource ]
|
||||
|
||||
actions:
|
||||
- action: grant
|
||||
|
||||
errors:
|
||||
- error: notAllowedToSetPermissions
|
||||
message: "not allowed to set permissions"
|
||||
File diff suppressed because it is too large
Load Diff
1211
system/service/auth_actions.gen.go
Normal file
1211
system/service/auth_actions.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
133
system/service/auth_actions.yaml
Normal file
133
system/service/auth_actions.yaml
Normal file
@ -0,0 +1,133 @@
|
||||
# List of security/audit events and errors that we need to log
|
||||
|
||||
resource: system:auth
|
||||
service: auth
|
||||
|
||||
# Default sensitivity for actions
|
||||
defaultActionSeverity: warning
|
||||
|
||||
# default severity for errors
|
||||
defaultErrorSeverity: alert
|
||||
|
||||
import:
|
||||
- github.com/cortezaproject/corteza-server/system/types
|
||||
|
||||
props:
|
||||
- name: email
|
||||
- name: provider
|
||||
- name: credentials
|
||||
type: "*types.Credentials"
|
||||
fields: [ kind, label, ID ]
|
||||
- name: role
|
||||
type: "*types.Role"
|
||||
fields: [ handle, name, ID ]
|
||||
- name: user
|
||||
type: "*types.User"
|
||||
fields: [ handle, name, ID, email, suspendedAt, deletedAt ]
|
||||
|
||||
actions:
|
||||
- action: authenticate
|
||||
log: "successfully authenticated with {credentials.kind}"
|
||||
|
||||
- action: issueToken
|
||||
log: "token '{credentials.kind}' issued"
|
||||
|
||||
- action: validateToken
|
||||
log: "token '{credentials.kind}' validated"
|
||||
|
||||
- action: changePassword
|
||||
log: "password changed"
|
||||
|
||||
- action: internalSignup
|
||||
log: "{user.email} signed-up"
|
||||
|
||||
- action: confirmEmail
|
||||
log: "email {user.email} confirmed"
|
||||
|
||||
- action: externalSignup
|
||||
log: "{user.email} signed-up after successful external authentication via {credentials.kind}"
|
||||
|
||||
- action: sendEmailConfirmationToken
|
||||
log: "confirmation notification sent to {email}"
|
||||
|
||||
- action: sendPasswordResetToken
|
||||
log: "password reset token sent to {email}"
|
||||
|
||||
- action: exchangePasswordResetToken
|
||||
log: "password reset token exchanged"
|
||||
|
||||
- action: autoPromote
|
||||
log: "auto-promoted to {role}"
|
||||
|
||||
- action: updateCredentials
|
||||
log: "credentials {credentials.kind} updated"
|
||||
|
||||
- action: createCredentials
|
||||
log: "new credentials {credentials.kind} created"
|
||||
|
||||
errors:
|
||||
- error: subscription
|
||||
message: "{err}"
|
||||
log: "{err}"
|
||||
severity: warning
|
||||
|
||||
- error: invalidCredentials
|
||||
message: "invalid username and password combination"
|
||||
log: "{email} failed to authenticate with {credentials.kind}"
|
||||
severity: warning
|
||||
|
||||
- error: invalidEmailFormat
|
||||
message: "invalid email"
|
||||
|
||||
- error: invalidHandle
|
||||
message: "invalid handle"
|
||||
|
||||
- error: failedForUnknownUser
|
||||
safe: invalidCredentials
|
||||
log: "unknown user {email} tried to log-in with {credentials.kind}"
|
||||
severity: warning
|
||||
|
||||
- error: failedForDisabledUser
|
||||
safe: invalidCredentials
|
||||
log: "disabled user {user} tried to log-in with {credentials.kind}"
|
||||
severity: warning
|
||||
|
||||
- error: failedUnconfirmedEmail
|
||||
message: "system requires confirmed email before logging in"
|
||||
log: "failed to log-in with with unconfirmed email"
|
||||
|
||||
- error: interalLoginDisabledByConfig
|
||||
message: "internal login (username/password) is disabled"
|
||||
|
||||
- error: internalSignupDisabledByConfig
|
||||
message: "internal sign-up (username/password) is disabled"
|
||||
|
||||
- error: passwordChangeFailedForUnknownUser
|
||||
message: "failed to change password for the unknown user"
|
||||
|
||||
- error: passwodResetFailedOldPasswordCheckFailed
|
||||
message: "failed to change password, old password does not match"
|
||||
|
||||
- error: passwordResetDisabledByConfig
|
||||
message: "password reset is disabled"
|
||||
|
||||
- error: passwordNotSecure
|
||||
message: "provided password is not secure; use longer password with more non-alphanumeric character"
|
||||
|
||||
- error: externalDisabledByConfig
|
||||
message: "external authentication (using external authentication provider) is disabled"
|
||||
log: "external authentication is disabled"
|
||||
severity: warning
|
||||
|
||||
- error: profileWithoutValidEmail
|
||||
message: "external authentication provider returned profile without valid email"
|
||||
log: "external authentication provider {credentials.kind} returned profile without valid email"
|
||||
severity: warning
|
||||
|
||||
- error: credentialsLinkedToInvalidUser
|
||||
message: "credentials {credentials.kind} linked to disabled or deleted user {user}"
|
||||
severity: warning
|
||||
|
||||
- error: invalidToken
|
||||
message: "invalid token"
|
||||
severity: warning
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
@ -33,8 +32,6 @@ func makeMockAuthService(u repository.UserRepository, c repository.CredentialsRe
|
||||
return nil
|
||||
},
|
||||
|
||||
logger: zap.NewNop(),
|
||||
|
||||
settings: &types.Settings{},
|
||||
|
||||
eventbus: eventbus.New(),
|
||||
@ -143,19 +140,18 @@ func Test_auth_validateInternalLogin(t *testing.T) {
|
||||
{name: "no email", args: args{"", ""}, wantErr: true},
|
||||
{name: "bad email", args: args{"test", ""}, wantErr: true},
|
||||
{name: "no pass", args: args{"test@domain.tld", ""}, wantErr: true},
|
||||
{name: "all good", args: args{"test@domain.tld", "password"}, wantErr: false},
|
||||
}
|
||||
|
||||
svc := auth{
|
||||
logger: zap.NewNop(),
|
||||
settings: &types.Settings{},
|
||||
//{name: "all good", args: args{"test@domain.tld", "password"}, wantErr: false},
|
||||
|
||||
// until we get proper mocking, DI for unit testing in place
|
||||
// this will have to do
|
||||
}
|
||||
|
||||
svc := makeMockAuthService(nil, nil)
|
||||
svc.settings.Auth.Internal.Enabled = true
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := svc.validateInternalLogin(tt.args.email, tt.args.password); (err != nil) != tt.wantErr {
|
||||
if _, err := svc.InternalLogin(tt.args.email, tt.args.password); (err != nil) != tt.wantErr {
|
||||
t.Errorf("auth.validateInternalLogin() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
@ -170,35 +166,35 @@ func Test_auth_checkPassword(t *testing.T) {
|
||||
cc types.CredentialsSet
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
name string
|
||||
args args
|
||||
rval bool
|
||||
}{
|
||||
{
|
||||
name: "empty set",
|
||||
wantErr: true,
|
||||
args: args{}},
|
||||
name: "empty set",
|
||||
rval: false,
|
||||
args: args{}},
|
||||
{
|
||||
name: "bad pwd",
|
||||
wantErr: true,
|
||||
name: "bad pwd",
|
||||
rval: false,
|
||||
args: args{
|
||||
password: " foo ",
|
||||
cc: types.CredentialsSet{&types.Credentials{ID: 1, Credentials: string(hashedPassword)}}}},
|
||||
{
|
||||
name: "invalid credentials",
|
||||
wantErr: true,
|
||||
name: "invalid credentials",
|
||||
rval: false,
|
||||
args: args{
|
||||
password: " foo ",
|
||||
cc: types.CredentialsSet{&types.Credentials{ID: 0, Credentials: string(hashedPassword)}}}},
|
||||
{
|
||||
name: "ok",
|
||||
wantErr: false,
|
||||
name: "ok",
|
||||
rval: true,
|
||||
args: args{
|
||||
password: plainPassword,
|
||||
cc: types.CredentialsSet{&types.Credentials{ID: 1, Credentials: string(hashedPassword)}}}},
|
||||
{
|
||||
name: "multipass",
|
||||
wantErr: false,
|
||||
name: "multipass",
|
||||
rval: true,
|
||||
args: args{
|
||||
password: plainPassword,
|
||||
cc: types.CredentialsSet{
|
||||
@ -210,14 +206,13 @@ func Test_auth_checkPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
svc := auth{
|
||||
logger: zap.NewNop(),
|
||||
settings: &types.Settings{},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := svc.checkPassword(tt.args.password, tt.args.cc); (err != nil) != tt.wantErr {
|
||||
t.Errorf("auth.checkPassword() error = %v, wantErr %v", err, tt.wantErr)
|
||||
if tt.rval != svc.checkPassword(tt.args.password, tt.args.cc) {
|
||||
t.Errorf("auth.checkPassword() expecting rval to be %v", tt.rval)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -232,43 +227,34 @@ func Test_auth_validateToken(t *testing.T) {
|
||||
args args
|
||||
wantID uint64
|
||||
wantCredentials string
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
{
|
||||
name: "empty",
|
||||
wantID: 0,
|
||||
wantCredentials: "",
|
||||
wantErr: true,
|
||||
args: args{token: ""}},
|
||||
{
|
||||
name: "foo",
|
||||
wantID: 0,
|
||||
wantCredentials: "",
|
||||
wantErr: true,
|
||||
args: args{token: "foo1"}},
|
||||
{
|
||||
name: "semivalid",
|
||||
wantID: 0,
|
||||
wantCredentials: "",
|
||||
wantErr: true,
|
||||
args: args{token: "foofoofoofoofoofoofoofoofoofoofo0"}},
|
||||
{
|
||||
name: "valid",
|
||||
wantID: 1,
|
||||
wantCredentials: "foofoofoofoofoofoofoofoofoofoofo",
|
||||
wantErr: false,
|
||||
args: args{token: "foofoofoofoofoofoofoofoofoofoofo1"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
svc := auth{}
|
||||
svc.logger = zap.NewNop()
|
||||
gotID, gotCredentials, err := svc.validateToken(tt.args.token)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("auth.validateToken() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
gotID, gotCredentials := svc.validateToken(tt.args.token)
|
||||
|
||||
if gotID != tt.wantID {
|
||||
t.Errorf("auth.validateToken() gotID = %v, want %v", gotID, tt.wantID)
|
||||
}
|
||||
|
||||
@ -2,32 +2,26 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza-server/pkg/permissions"
|
||||
"github.com/cortezaproject/corteza-server/system/repository"
|
||||
"github.com/cortezaproject/corteza-server/system/service/event"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrRoleNameNotUnique = serviceError("RoleNameNotUnique")
|
||||
ErrRoleHandleNotUnique = serviceError("RoleHandleNotUnique")
|
||||
)
|
||||
|
||||
type (
|
||||
role struct {
|
||||
db *factory.DB
|
||||
ctx context.Context
|
||||
logger *zap.Logger
|
||||
db *factory.DB
|
||||
ctx context.Context
|
||||
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
ac roleAccessController
|
||||
eventbus eventDispatcher
|
||||
@ -79,8 +73,10 @@ func Role(ctx context.Context) RoleService {
|
||||
return (&role{
|
||||
ac: DefaultAccessControl,
|
||||
eventbus: eventbus.Service(),
|
||||
logger: DefaultLogger.Named("role"),
|
||||
user: DefaultUser.With(ctx),
|
||||
|
||||
actionlog: DefaultActionlog,
|
||||
|
||||
user: DefaultUser.With(ctx),
|
||||
}).With(ctx)
|
||||
}
|
||||
|
||||
@ -90,7 +86,8 @@ func (svc role) With(ctx context.Context) RoleService {
|
||||
db: db,
|
||||
ctx: ctx,
|
||||
|
||||
logger: svc.logger,
|
||||
actionlog: svc.actionlog,
|
||||
|
||||
ac: svc.ac,
|
||||
eventbus: svc.eventbus,
|
||||
user: svc.user,
|
||||
@ -99,90 +96,116 @@ func (svc role) With(ctx context.Context) RoleService {
|
||||
}
|
||||
}
|
||||
|
||||
func (svc role) log(ctx context.Context, fields ...zapcore.Field) *zap.Logger {
|
||||
return logger.AddRequestID(ctx, svc.logger).With(fields...)
|
||||
func (svc role) Find(filter types.RoleFilter) (rr types.RoleSet, f types.RoleFilter, err error) {
|
||||
var (
|
||||
raProps = &roleActionProps{filter: &filter}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() error {
|
||||
filter.IsReadable = svc.ac.FilterReadableRoles(svc.ctx)
|
||||
|
||||
if filter.Deleted > 0 {
|
||||
// If list with deleted or suspended users is requested
|
||||
// user must have access permissions to system (ie: is admin)
|
||||
//
|
||||
// not the best solution but ATM it allows us to have at least
|
||||
// some kind of control over who can see deleted or archived roles
|
||||
if !svc.ac.CanAccess(svc.ctx) {
|
||||
return RoleErrNotAllowedToListRoles()
|
||||
}
|
||||
}
|
||||
|
||||
rr, f, err = svc.role.Find(filter)
|
||||
return err
|
||||
})
|
||||
|
||||
return rr, f, svc.recordAction(svc.ctx, raProps, RoleActionSearch, err)
|
||||
}
|
||||
|
||||
func (svc role) FindByID(roleID uint64) (*types.Role, error) {
|
||||
return svc.findByID(roleID)
|
||||
func (svc role) FindByID(roleID uint64) (r *types.Role, err error) {
|
||||
var (
|
||||
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() error {
|
||||
r, err = svc.findByID(roleID)
|
||||
raProps.setRole(r)
|
||||
return err
|
||||
})
|
||||
|
||||
return r, svc.recordAction(svc.ctx, raProps, RoleActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc role) findByID(roleID uint64) (*types.Role, error) {
|
||||
if roleID == 0 {
|
||||
return nil, ErrInvalidID.withStack()
|
||||
return nil, RoleErrInvalidID()
|
||||
}
|
||||
|
||||
role, err := svc.role.FindByID(roleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !svc.ac.CanReadRole(svc.ctx, role) {
|
||||
return nil, ErrNoPermissions.withStack()
|
||||
}
|
||||
return role, nil
|
||||
return svc.role.FindByID(roleID)
|
||||
}
|
||||
|
||||
func (svc role) Find(f types.RoleFilter) (types.RoleSet, types.RoleFilter, error) {
|
||||
f.IsReadable = svc.ac.FilterReadableRoles(svc.ctx)
|
||||
func (svc role) FindByName(name string) (r *types.Role, err error) {
|
||||
var (
|
||||
raProps = &roleActionProps{role: &types.Role{Name: name}}
|
||||
)
|
||||
|
||||
if f.Deleted > 0 {
|
||||
// If list with deleted or suspended users is requested
|
||||
// user must have access permissions to system (ie: is admin)
|
||||
//
|
||||
// not the best solution but ATM it allows us to have at least
|
||||
// some kind of control over who can see deleted or archived roles
|
||||
if !svc.ac.CanAccess(svc.ctx) {
|
||||
return nil, f, ErrNoPermissions.withStack()
|
||||
}
|
||||
}
|
||||
err = svc.db.Transaction(func() error {
|
||||
r, err = svc.role.FindByName(name)
|
||||
raProps.setRole(r)
|
||||
return err
|
||||
})
|
||||
|
||||
return svc.role.Find(f)
|
||||
return r, svc.recordAction(svc.ctx, raProps, RoleActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc role) FindByName(rolename string) (*types.Role, error) {
|
||||
return svc.role.FindByName(rolename)
|
||||
}
|
||||
func (svc role) FindByHandle(h string) (r *types.Role, err error) {
|
||||
var (
|
||||
raProps = &roleActionProps{role: &types.Role{Handle: h}}
|
||||
)
|
||||
|
||||
func (svc role) FindByHandle(handle string) (*types.Role, error) {
|
||||
return svc.role.FindByHandle(handle)
|
||||
err = svc.db.Transaction(func() error {
|
||||
r, err = svc.role.FindByName(h)
|
||||
raProps.setRole(r)
|
||||
return err
|
||||
})
|
||||
|
||||
return r, svc.recordAction(svc.ctx, raProps, RoleActionLookup, err)
|
||||
}
|
||||
|
||||
// FindByAny finds role by given identifier (id, handle, name)
|
||||
func (svc role) FindByAny(identifier interface{}) (r *types.Role, err error) {
|
||||
if ID, ok := identifier.(uint64); ok {
|
||||
r, err = svc.FindByID(ID)
|
||||
return svc.FindByID(ID)
|
||||
} else if strIdentifier, ok := identifier.(string); ok {
|
||||
if ID, _ := strconv.ParseUint(strIdentifier, 10, 64); ID > 0 {
|
||||
r, err = svc.FindByID(ID)
|
||||
return svc.FindByID(ID)
|
||||
} else {
|
||||
r, err = svc.FindByHandle(strIdentifier)
|
||||
if err == nil && r.ID == 0 {
|
||||
r, err = svc.FindByName(strIdentifier)
|
||||
return svc.FindByName(strIdentifier)
|
||||
}
|
||||
|
||||
return r, err
|
||||
}
|
||||
} else {
|
||||
err = ErrInvalidID.withStack()
|
||||
return nil, RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc role) Create(new *types.Role) (r *types.Role, err error) {
|
||||
var (
|
||||
raProps = &roleActionProps{new: new}
|
||||
)
|
||||
|
||||
if !handle.IsValid(new.Handle) {
|
||||
return nil, ErrInvalidHandle
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if !handle.IsValid(new.Handle) {
|
||||
return RoleErrInvalidHandle()
|
||||
}
|
||||
|
||||
if !svc.ac.CanCreateRole(svc.ctx) {
|
||||
return nil, ErrNoCreatePermissions.withStack()
|
||||
}
|
||||
if !svc.ac.CanCreateRole(svc.ctx) {
|
||||
return RoleErrNotAllowedToCreate()
|
||||
}
|
||||
|
||||
return r, svc.db.Transaction(func() (err error) {
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.RoleBeforeCreate(new, r)); err != nil {
|
||||
return
|
||||
}
|
||||
@ -195,29 +218,40 @@ func (svc role) Create(new *types.Role) (r *types.Role, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.RoleAfterCreate(new, r))
|
||||
return
|
||||
})
|
||||
|
||||
return r, svc.recordAction(svc.ctx, raProps, RoleActionCreate, err)
|
||||
|
||||
}
|
||||
|
||||
func (svc role) Update(upd *types.Role) (r *types.Role, err error) {
|
||||
if upd.ID == 0 {
|
||||
return nil, ErrInvalidID.withStack()
|
||||
}
|
||||
var (
|
||||
raProps = &roleActionProps{update: upd}
|
||||
)
|
||||
|
||||
if !handle.IsValid(upd.Handle) {
|
||||
return nil, ErrInvalidHandle.withStack()
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if upd.ID == 0 {
|
||||
return RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, upd) {
|
||||
return nil, ErrNoUpdatePermissions.withStack()
|
||||
}
|
||||
if !handle.IsValid(upd.Handle) {
|
||||
return RoleErrInvalidHandle()
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, upd) {
|
||||
return RoleErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
return r, svc.db.Transaction(func() (err error) {
|
||||
if r, err = svc.role.FindByID(upd.ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.RoleBeforeUpdate(upd, r)); err != nil {
|
||||
return
|
||||
}
|
||||
@ -226,10 +260,10 @@ func (svc role) Update(upd *types.Role) (r *types.Role, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Assign changed values
|
||||
r.Name = upd.Name
|
||||
r.Handle = upd.Handle
|
||||
r.Name = upd.Name
|
||||
|
||||
// Assign changed values
|
||||
if r, err = svc.role.Update(r); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -238,18 +272,26 @@ func (svc role) Update(upd *types.Role) (r *types.Role, err error) {
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return r, svc.recordAction(svc.ctx, raProps, RoleActionUpdate, err)
|
||||
}
|
||||
|
||||
func (svc role) UniqueCheck(r *types.Role) (err error) {
|
||||
var (
|
||||
raProps = &roleActionProps{role: r}
|
||||
)
|
||||
|
||||
if r.Handle != "" {
|
||||
if ex, _ := svc.role.FindByHandle(r.Handle); ex != nil && ex.ID > 0 && ex.ID != r.ID {
|
||||
return ErrRoleHandleNotUnique
|
||||
raProps.setExisting(ex)
|
||||
return RoleErrHandleNotUnique()
|
||||
}
|
||||
}
|
||||
|
||||
if r.Name != "" {
|
||||
if ex, _ := svc.role.FindByName(r.Name); ex != nil && ex.ID > 0 && ex.ID != r.ID {
|
||||
return ErrRoleNameNotUnique
|
||||
raProps.setExisting(ex)
|
||||
return RoleErrNameNotUnique()
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,178 +300,300 @@ func (svc role) UniqueCheck(r *types.Role) (err error) {
|
||||
|
||||
func (svc role) Delete(roleID uint64) (err error) {
|
||||
var (
|
||||
role *types.Role
|
||||
r *types.Role
|
||||
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
|
||||
)
|
||||
|
||||
if role, err = svc.findByID(roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if r, err = svc.findByID(roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !svc.ac.CanDeleteRole(svc.ctx, role) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanDeleteRole(svc.ctx, r) {
|
||||
return RoleErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.RoleBeforeDelete(nil, r)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.role.DeleteByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.RoleAfterDelete(nil, r))
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.RoleBeforeDelete(nil, role)); err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, raProps, RoleActionDelete, err)
|
||||
}
|
||||
|
||||
func (svc role) Undelete(roleID uint64) (err error) {
|
||||
var (
|
||||
r *types.Role
|
||||
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if r, err = svc.findByID(roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanDeleteRole(svc.ctx, r) {
|
||||
return RoleErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if err = svc.role.UndeleteByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, raProps, RoleActionUndelete, err)
|
||||
}
|
||||
|
||||
func (svc role) Archive(roleID uint64) (err error) {
|
||||
var (
|
||||
r *types.Role
|
||||
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if r, err = svc.findByID(roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, r) {
|
||||
return RoleErrNotAllowedToArchive()
|
||||
}
|
||||
|
||||
if err = svc.role.ArchiveByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.role.DeleteByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.RoleAfterDelete(nil, role))
|
||||
return
|
||||
return svc.recordAction(svc.ctx, raProps, RoleActionArchive, err)
|
||||
}
|
||||
|
||||
func (svc role) Undelete(roleID uint64) error {
|
||||
role, err := svc.findByID(roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (svc role) Unarchive(roleID uint64) (err error) {
|
||||
var (
|
||||
r *types.Role
|
||||
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
|
||||
)
|
||||
|
||||
if !svc.ac.CanDeleteRole(svc.ctx, role) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if r, err = svc.findByID(roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.role.UndeleteByID(roleID)
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanDeleteRole(svc.ctx, r) {
|
||||
return RoleErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if err = svc.role.UndeleteByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, raProps, RoleActionUnarchive, err)
|
||||
}
|
||||
|
||||
func (svc role) Archive(roleID uint64) error {
|
||||
role, err := svc.findByID(roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (svc role) Merge(roleID, targetRoleID uint64) (err error) {
|
||||
var (
|
||||
r *types.Role
|
||||
t *types.Role
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, role) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
raProps = &roleActionProps{
|
||||
role: &types.Role{ID: roleID},
|
||||
target: &types.Role{ID: targetRoleID},
|
||||
}
|
||||
)
|
||||
|
||||
return svc.role.ArchiveByID(roleID)
|
||||
}
|
||||
|
||||
func (svc role) Unarchive(roleID uint64) error {
|
||||
role, err := svc.findByID(roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, role) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
|
||||
return svc.role.UnarchiveByID(roleID)
|
||||
}
|
||||
|
||||
func (svc role) Merge(roleID, targetRoleID uint64) error {
|
||||
role, err := svc.findByID(roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if targetRoleID == 0 {
|
||||
return ErrInvalidID.withStack()
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, role) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
|
||||
return svc.role.MergeByID(roleID, targetRoleID)
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if roleID == 0 || targetRoleID == 0 {
|
||||
return RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if r, err = svc.findByID(roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, r) {
|
||||
return RoleErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
if t, err = svc.findByID(targetRoleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raProps.setTarget(t)
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, t) {
|
||||
return RoleErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
if err = svc.role.MergeByID(roleID, targetRoleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, raProps, RoleActionMerge, err)
|
||||
}
|
||||
|
||||
// Move
|
||||
//
|
||||
// @obsolete
|
||||
func (svc role) Move(roleID, targetOrganisationID uint64) error {
|
||||
role, err := svc.findByID(roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if targetOrganisationID == 0 {
|
||||
return ErrInvalidID.withStack()
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateRole(svc.ctx, role) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
|
||||
return svc.role.MoveByID(roleID, targetOrganisationID)
|
||||
return RoleErrGeneric().Wrap(fmt.Errorf("obsolete"))
|
||||
}
|
||||
|
||||
func (svc role) Membership(userID uint64) ([]*types.RoleMember, error) {
|
||||
return svc.role.MembershipsFindByUserID(userID)
|
||||
}
|
||||
|
||||
func (svc role) MemberList(roleID uint64) ([]*types.RoleMember, error) {
|
||||
if roleID == permissions.EveryoneRoleID {
|
||||
return nil, ErrInvalidID.withStack()
|
||||
}
|
||||
|
||||
_, err := svc.findByID(roleID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc.role.MemberFindByRoleID(roleID)
|
||||
}
|
||||
|
||||
func (svc role) MemberAdd(roleID, userID uint64) (err error) {
|
||||
func (svc role) MemberList(roleID uint64) (mm []*types.RoleMember, err error) {
|
||||
var (
|
||||
role *types.Role
|
||||
user *types.User
|
||||
r *types.Role
|
||||
|
||||
raProps = &roleActionProps{
|
||||
role: &types.Role{ID: roleID},
|
||||
}
|
||||
)
|
||||
|
||||
if role, err = svc.findByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
err = svc.db.Transaction(func() error {
|
||||
if roleID == permissions.EveryoneRoleID || roleID == 0 {
|
||||
return RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if user, err = svc.user.FindByID(userID); err != nil {
|
||||
return
|
||||
}
|
||||
if r, err = svc.findByID(roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.RoleMemberBeforeAdd(user, role)); err != nil {
|
||||
return
|
||||
}
|
||||
if !svc.ac.CanReadRole(svc.ctx, r) {
|
||||
return RoleErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
if !svc.ac.CanManageRoleMembers(svc.ctx, role) {
|
||||
return errors.New("Not allowed to manage role members")
|
||||
}
|
||||
if mm, err = svc.role.MemberFindByRoleID(roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = svc.role.MemberAddByID(role.ID, user.ID); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.RoleMemberAfterAdd(user, role))
|
||||
return nil
|
||||
return mm, svc.recordAction(svc.ctx, raProps, RoleActionMembers, err)
|
||||
}
|
||||
|
||||
func (svc role) MemberRemove(roleID, userID uint64) (err error) {
|
||||
// MemberAdd adds member (user) to a role
|
||||
func (svc role) MemberAdd(roleID, memberID uint64) (err error) {
|
||||
var (
|
||||
role *types.Role
|
||||
user *types.User
|
||||
r *types.Role
|
||||
m *types.User
|
||||
|
||||
raProps = &roleActionProps{
|
||||
role: &types.Role{ID: roleID},
|
||||
member: &types.User{ID: memberID},
|
||||
}
|
||||
)
|
||||
|
||||
if role, err = svc.findByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if roleID == permissions.EveryoneRoleID || roleID == 0 || memberID == 0 {
|
||||
return RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if user, err = svc.user.FindByID(userID); err != nil {
|
||||
return
|
||||
}
|
||||
if r, err = svc.findByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.RoleMemberBeforeRemove(user, role)); err != nil {
|
||||
return
|
||||
}
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanManageRoleMembers(svc.ctx, role) {
|
||||
return errors.New("Not allowed to manage role members")
|
||||
}
|
||||
if m, err = svc.user.FindByID(memberID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.role.MemberRemoveByID(role.ID, user.ID); err != nil {
|
||||
return
|
||||
}
|
||||
raProps.setMember(m)
|
||||
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.RoleMemberAfterRemove(user, role))
|
||||
return nil
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.RoleMemberBeforeAdd(m, r)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.ac.CanManageRoleMembers(svc.ctx, r) {
|
||||
return RoleErrNotAllowedToManageMembers()
|
||||
}
|
||||
|
||||
if err = svc.role.MemberAddByID(r.ID, m.ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.RoleMemberAfterAdd(m, r))
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, raProps, RoleActionMemberAdd, err)
|
||||
}
|
||||
|
||||
// MemberRemove removes member (user) from a role
|
||||
func (svc role) MemberRemove(roleID, memberID uint64) (err error) {
|
||||
var (
|
||||
r *types.Role
|
||||
m *types.User
|
||||
raProps = &roleActionProps{
|
||||
role: &types.Role{ID: roleID},
|
||||
member: &types.User{ID: memberID},
|
||||
}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if roleID == permissions.EveryoneRoleID || roleID == 0 || memberID == 0 {
|
||||
return RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if r, err = svc.findByID(roleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if m, err = svc.user.FindByID(memberID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raProps.setMember(m)
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.RoleMemberBeforeRemove(m, r)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.ac.CanManageRoleMembers(svc.ctx, r) {
|
||||
return RoleErrNotAllowedToManageMembers()
|
||||
}
|
||||
|
||||
if err = svc.role.MemberRemoveByID(r.ID, m.ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.RoleMemberAfterRemove(m, r))
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, raProps, RoleActionMemberRemove, err)
|
||||
}
|
||||
|
||||
var _ RoleService = &role{}
|
||||
|
||||
1171
system/service/role_actions.gen.go
Normal file
1171
system/service/role_actions.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
135
system/service/role_actions.yaml
Normal file
135
system/service/role_actions.yaml
Normal file
@ -0,0 +1,135 @@
|
||||
# List of loggable service actions
|
||||
|
||||
resource: system:role
|
||||
service: role
|
||||
|
||||
# Default sensitivity for actions
|
||||
defaultActionSeverity: notice
|
||||
|
||||
# default severity for errors
|
||||
defaultErrorSeverity: alert
|
||||
|
||||
import:
|
||||
- github.com/cortezaproject/corteza-server/system/types
|
||||
|
||||
props:
|
||||
- name: member
|
||||
type: "*types.User"
|
||||
fields: [ handle, email, name, ID ]
|
||||
- name: role
|
||||
type: "*types.Role"
|
||||
fields: [ handle, name, ID ]
|
||||
- name: new
|
||||
type: "*types.Role"
|
||||
fields: [ handle, name, ID ]
|
||||
- name: update
|
||||
type: "*types.Role"
|
||||
fields: [ handle, name, ID ]
|
||||
- name: existing
|
||||
type: "*types.Role"
|
||||
fields: [ handle, name, ID ]
|
||||
- name: target
|
||||
type: "*types.Role"
|
||||
fields: [ handle, name, ID ]
|
||||
- name: filter
|
||||
type: "*types.RoleFilter"
|
||||
fields: [ query, roleID, memberID, handle, name, deleted, archived, sort ]
|
||||
|
||||
actions:
|
||||
- action: search
|
||||
log: "searched for roles"
|
||||
severity: info
|
||||
|
||||
- action: lookup
|
||||
log: "looked-up for a {role}"
|
||||
severity: info
|
||||
|
||||
- action: create
|
||||
log: "created {role}"
|
||||
|
||||
- action: update
|
||||
log: "updated {role}"
|
||||
|
||||
- action: delete
|
||||
log: "deleted {role}"
|
||||
|
||||
- action: undelete
|
||||
log: "undeleted {role}"
|
||||
|
||||
- action: archive
|
||||
log: "archived {role}"
|
||||
|
||||
- action: unarchive
|
||||
log: "unarchived {role}"
|
||||
|
||||
- action: merge
|
||||
log: "merged {target} with {role}"
|
||||
|
||||
- action: members
|
||||
log: "searched for members of {role}"
|
||||
|
||||
- action: memberAdd
|
||||
log: "added {member.email} to {role}"
|
||||
|
||||
- action: memberRemove
|
||||
log: "removed {member.email} from {role}"
|
||||
|
||||
|
||||
errors:
|
||||
- error: nonexistent
|
||||
message: "role does not exist"
|
||||
severity: warning
|
||||
|
||||
- error: invalidID
|
||||
message: "invalid ID"
|
||||
severity: warning
|
||||
|
||||
- error: invalidHandle
|
||||
message: "invalid handle"
|
||||
severity: warning
|
||||
|
||||
- error: notAllowedToRead
|
||||
message: "not allowed to read role"
|
||||
log: "failed to read {role.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToListRoles
|
||||
message: "not allowed to list roles"
|
||||
log: "failed to list role; insufficient permissions"
|
||||
|
||||
- error: notAllowedToCreate
|
||||
message: "not allowed to create role"
|
||||
log: "failed to create role; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUpdate
|
||||
message: "not allowed to update role"
|
||||
log: "failed to update {role.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToDelete
|
||||
message: "not allowed to delete role"
|
||||
log: "failed to delete {role.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUndelete
|
||||
message: "not allowed to undelete role"
|
||||
log: "failed to undelete {role.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToArchive
|
||||
message: "not allowed to archive role"
|
||||
log: "failed to archive {role.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUnarchive
|
||||
message: "not allowed to unarchive role"
|
||||
log: "failed to unarchive {role.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToManageMembers
|
||||
message: "not allowed to manage role members"
|
||||
log: "failed to manage {role.handle} members; insufficient permissions"
|
||||
|
||||
- error: handleNotUnique
|
||||
message: "role handle not unique"
|
||||
log: "used duplicate handle ({role.handle}) for role"
|
||||
severity: warning
|
||||
|
||||
- error: nameNotUnique
|
||||
message: "role name not unique"
|
||||
log: "used duplicate name ({role.name}) for role"
|
||||
severity: warning
|
||||
@ -2,17 +2,19 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/pkg/store"
|
||||
"github.com/cortezaproject/corteza-server/pkg/store/minio"
|
||||
"github.com/cortezaproject/corteza-server/pkg/store/plain"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
actionlogRepository "github.com/cortezaproject/corteza-server/pkg/actionlog/repository"
|
||||
"github.com/cortezaproject/corteza-server/pkg/app/options"
|
||||
intAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/permissions"
|
||||
"github.com/cortezaproject/corteza-server/pkg/settings"
|
||||
"github.com/cortezaproject/corteza-server/pkg/store"
|
||||
"github.com/cortezaproject/corteza-server/pkg/store/minio"
|
||||
"github.com/cortezaproject/corteza-server/pkg/store/plain"
|
||||
"github.com/cortezaproject/corteza-server/system/repository"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
@ -74,6 +76,8 @@ var (
|
||||
// CurrentSettings represents current system settings
|
||||
CurrentSettings = &types.Settings{}
|
||||
|
||||
DefaultActionlog actionlog.Recorder
|
||||
|
||||
DefaultSink *sink
|
||||
|
||||
DefaultAuth AuthService
|
||||
@ -90,6 +94,12 @@ var (
|
||||
func Initialize(ctx context.Context, log *zap.Logger, c Config) (err error) {
|
||||
DefaultLogger = log.Named("service")
|
||||
|
||||
DefaultActionlog = actionlog.NewService(
|
||||
actionlogRepository.Mysql(repository.DB(ctx), "sys_actionlog"),
|
||||
log,
|
||||
log,
|
||||
)
|
||||
|
||||
if DefaultPermissions == nil {
|
||||
// Do not override permissions service stored under DefaultPermissions
|
||||
// to allow integration tests to inject own permission service
|
||||
|
||||
@ -2,45 +2,36 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
"io"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/permissions"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
"github.com/cortezaproject/corteza-server/system/repository"
|
||||
"github.com/cortezaproject/corteza-server/system/service/event"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrUserInvalidCredentials = serviceError("UserInvalidCredentials")
|
||||
ErrUserHandleNotUnique = serviceError("UserHandleNotUnique")
|
||||
ErrUserUsernameNotUnique = serviceError("UserUsernameNotUnique")
|
||||
ErrUserEmailNotUnique = serviceError("UserEmailNotUnique")
|
||||
ErrUserInvalidEmail = serviceError("UserInvalidEmail")
|
||||
ErrUserLocked = serviceError("UserLocked")
|
||||
|
||||
maskPrivateDataEmail = "####.#######@######.###"
|
||||
maskPrivateDataName = "##### ##########"
|
||||
)
|
||||
|
||||
type (
|
||||
user struct {
|
||||
db *factory.DB
|
||||
ctx context.Context
|
||||
logger *zap.Logger
|
||||
db *factory.DB
|
||||
ctx context.Context
|
||||
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
settings *types.Settings
|
||||
|
||||
@ -56,7 +47,7 @@ type (
|
||||
}
|
||||
|
||||
userAuth interface {
|
||||
checkPasswordStrength(string) error
|
||||
checkPasswordStrength(string) bool
|
||||
changePassword(uint64, string) error
|
||||
}
|
||||
|
||||
@ -79,6 +70,10 @@ type (
|
||||
FilterUsersWithUnmaskableName(ctx context.Context) *permissions.ResourceFilter
|
||||
}
|
||||
|
||||
// Temp types to support user.Preloader
|
||||
userIdGetter func(chan uint64)
|
||||
userSetter func(*types.User) error
|
||||
|
||||
UserService interface {
|
||||
With(ctx context.Context) UserService
|
||||
|
||||
@ -101,33 +96,32 @@ type (
|
||||
Undelete(id uint64) error
|
||||
|
||||
SetPassword(userID uint64, password string) error
|
||||
|
||||
Preloader(userIdGetter, types.UserFilter, userSetter) error
|
||||
}
|
||||
)
|
||||
|
||||
func User(ctx context.Context) UserService {
|
||||
return (&user{
|
||||
logger: DefaultLogger.Named("user"),
|
||||
eventbus: eventbus.Service(),
|
||||
ac: DefaultAccessControl,
|
||||
settings: CurrentSettings,
|
||||
auth: DefaultAuth,
|
||||
|
||||
actionlog: DefaultActionlog,
|
||||
|
||||
subscription: CurrentSubscription,
|
||||
}).With(ctx)
|
||||
}
|
||||
|
||||
// log() returns zap's logger with requestID from current context and fields.
|
||||
func (svc user) log(ctx context.Context, fields ...zapcore.Field) *zap.Logger {
|
||||
return logger.AddRequestID(ctx, svc.logger).With(fields...)
|
||||
}
|
||||
|
||||
func (svc user) With(ctx context.Context) UserService {
|
||||
db := repository.DB(ctx)
|
||||
|
||||
return &user{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
logger: svc.logger,
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
|
||||
actionlog: svc.actionlog,
|
||||
|
||||
ac: svc.ac,
|
||||
eventbus: svc.eventbus,
|
||||
@ -141,33 +135,70 @@ func (svc user) With(ctx context.Context) UserService {
|
||||
}
|
||||
}
|
||||
|
||||
func (svc user) FindByID(ID uint64) (*types.User, error) {
|
||||
if ID == 0 {
|
||||
return nil, ErrInvalidID.withStack()
|
||||
}
|
||||
func (svc user) FindByID(userID uint64) (u *types.User, err error) {
|
||||
var (
|
||||
uaProps = &userActionProps{user: &types.User{ID: userID}}
|
||||
)
|
||||
|
||||
tmp := internalAuth.NewIdentity(ID)
|
||||
if internalAuth.IsSuperUser(tmp) {
|
||||
// Handling case when looking for a super-user
|
||||
//
|
||||
// Currently, superuser is a virtual entity
|
||||
// and does not exists in the db
|
||||
return &types.User{ID: ID}, nil
|
||||
}
|
||||
err = svc.db.Transaction(func() error {
|
||||
if userID == 0 {
|
||||
return UserErrInvalidID()
|
||||
}
|
||||
|
||||
return svc.proc(svc.user.FindByID(ID))
|
||||
su := internalAuth.NewIdentity(userID)
|
||||
if internalAuth.IsSuperUser(su) {
|
||||
// Handling case when looking for a super-user
|
||||
//
|
||||
// Currently, superuser is a virtual entity
|
||||
// and does not exists in the db
|
||||
u = &types.User{ID: userID}
|
||||
return nil
|
||||
}
|
||||
|
||||
u, err = svc.proc(svc.user.FindByID(userID))
|
||||
return err
|
||||
})
|
||||
|
||||
return u, svc.recordAction(svc.ctx, uaProps, UserActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc user) FindByEmail(email string) (*types.User, error) {
|
||||
return svc.proc(svc.user.FindByEmail(email))
|
||||
func (svc user) FindByEmail(email string) (u *types.User, err error) {
|
||||
var (
|
||||
uaProps = &userActionProps{user: &types.User{Email: email}}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() error {
|
||||
u, err = svc.proc(svc.user.FindByEmail(email))
|
||||
return err
|
||||
})
|
||||
|
||||
return u, svc.recordAction(svc.ctx, uaProps, UserActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc user) FindByUsername(username string) (*types.User, error) {
|
||||
return svc.proc(svc.user.FindByUsername(username))
|
||||
func (svc user) FindByUsername(username string) (u *types.User, err error) {
|
||||
var (
|
||||
uaProps = &userActionProps{user: &types.User{Username: username}}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() error {
|
||||
u, err = svc.proc(svc.user.FindByUsername(username))
|
||||
return err
|
||||
})
|
||||
|
||||
return u, svc.recordAction(svc.ctx, uaProps, UserActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc user) FindByHandle(handle string) (*types.User, error) {
|
||||
return svc.proc(svc.user.FindByHandle(handle))
|
||||
func (svc user) FindByHandle(handle string) (u *types.User, err error) {
|
||||
var (
|
||||
uaProps = &userActionProps{user: &types.User{Handle: handle}}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() error {
|
||||
u, err = svc.proc(svc.user.FindByHandle(handle))
|
||||
return err
|
||||
})
|
||||
|
||||
return u, svc.recordAction(svc.ctx, uaProps, UserActionLookup, err)
|
||||
}
|
||||
|
||||
// FindByAny finds user by given identifier (context, id, handle, email)
|
||||
@ -189,7 +220,7 @@ func (svc user) FindByAny(identifier interface{}) (u *types.User, err error) {
|
||||
u, err = svc.FindByHandle(strIdentifier)
|
||||
}
|
||||
} else {
|
||||
err = ErrInvalidID.withStack()
|
||||
err = UserErrInvalidID()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -215,74 +246,80 @@ func (svc user) proc(u *types.User, err error) (*types.User, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (svc user) Find(f types.UserFilter) (types.UserSet, types.UserFilter, error) {
|
||||
if f.Deleted > 0 {
|
||||
// If list with deleted users is requested
|
||||
// user must have access permissions to system (ie: is admin)
|
||||
//
|
||||
// not the best solution but ATM it allows us to have at least
|
||||
// some kind of control over who can see deleted users
|
||||
if !svc.ac.CanAccess(svc.ctx) {
|
||||
return nil, f, ErrNoPermissions.withStack()
|
||||
func (svc user) Find(filter types.UserFilter) (uu types.UserSet, f types.UserFilter, err error) {
|
||||
var (
|
||||
uaProps = &userActionProps{filter: &filter}
|
||||
)
|
||||
|
||||
err = svc.db.Transaction(func() error {
|
||||
if filter.Deleted > 0 {
|
||||
// If list with deleted users is requested
|
||||
// user must have access permissions to system (ie: is admin)
|
||||
//
|
||||
// not the best solution but ATM it allows us to have at least
|
||||
// some kind of control over who can see deleted users
|
||||
if !svc.ac.CanAccess(svc.ctx) {
|
||||
return UserErrNotAllowedToListUsers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare filter for email unmasking check
|
||||
f.IsEmailUnmaskable = svc.ac.FilterUsersWithUnmaskableEmail(svc.ctx)
|
||||
// Prepare filter for email unmasking check
|
||||
filter.IsEmailUnmaskable = svc.ac.FilterUsersWithUnmaskableEmail(svc.ctx)
|
||||
|
||||
// Prepare filter for name unmasking check
|
||||
f.IsNameUnmaskable = svc.ac.FilterUsersWithUnmaskableName(svc.ctx)
|
||||
// Prepare filter for name unmasking check
|
||||
filter.IsNameUnmaskable = svc.ac.FilterUsersWithUnmaskableName(svc.ctx)
|
||||
|
||||
f.IsReadable = svc.ac.FilterReadableUsers(svc.ctx)
|
||||
filter.IsReadable = svc.ac.FilterReadableUsers(svc.ctx)
|
||||
|
||||
return svc.procSet(svc.user.Find(f))
|
||||
}
|
||||
uu, f, err = svc.user.Find(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (svc user) procSet(u types.UserSet, f types.UserFilter, err error) (types.UserSet, types.UserFilter, error) {
|
||||
if err != nil {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
_ = u.Walk(func(u *types.User) error {
|
||||
svc.handlePrivateData(u)
|
||||
return nil
|
||||
return uu.Walk(func(u *types.User) error {
|
||||
svc.handlePrivateData(u)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
return u, f, nil
|
||||
return uu, f, svc.recordAction(svc.ctx, uaProps, UserActionSearch, err)
|
||||
}
|
||||
|
||||
func (svc user) Create(new *types.User) (u *types.User, err error) {
|
||||
if !svc.ac.CanCreateUser(svc.ctx) {
|
||||
return nil, ErrNoCreatePermissions.withStack()
|
||||
}
|
||||
var (
|
||||
uaProps = &userActionProps{new: new}
|
||||
)
|
||||
|
||||
if !handle.IsValid(new.Handle) {
|
||||
return nil, ErrInvalidHandle.withStack()
|
||||
}
|
||||
|
||||
if _, err := mail.ParseAddress(new.Email); err != nil {
|
||||
return nil, ErrUserInvalidEmail.withStack()
|
||||
}
|
||||
|
||||
if svc.subscription != nil {
|
||||
// When we have an active subscription, we need to check
|
||||
// if users can be creare or did this deployment hit
|
||||
// it's user-limit
|
||||
err = svc.subscription.CanCreateUser(svc.user.Total())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if !svc.ac.CanCreateUser(svc.ctx) {
|
||||
return UserErrNotAllowedToCreate()
|
||||
}
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.UserBeforeCreate(new, u)); err != nil {
|
||||
return
|
||||
}
|
||||
if !handle.IsValid(new.Handle) {
|
||||
return UserErrInvalidHandle()
|
||||
}
|
||||
|
||||
if new.Handle == "" {
|
||||
createHandle(svc.user, new)
|
||||
}
|
||||
if _, err := mail.ParseAddress(new.Email); err != nil {
|
||||
return UserErrInvalidEmail()
|
||||
}
|
||||
|
||||
return u, svc.db.Transaction(func() (err error) {
|
||||
if svc.subscription != nil {
|
||||
// When we have an active subscription, we need to check
|
||||
// if users can be create or did this deployment hit
|
||||
// it's user-limit
|
||||
err = svc.subscription.CanCreateUser(svc.user.Total())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.UserBeforeCreate(new, u)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if new.Handle == "" {
|
||||
createHandle(svc.user, new)
|
||||
}
|
||||
|
||||
if err = svc.UniqueCheck(new); err != nil {
|
||||
return
|
||||
@ -295,6 +332,8 @@ func (svc user) Create(new *types.User) (u *types.User, err error) {
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.UserAfterCreate(new, u))
|
||||
return
|
||||
})
|
||||
|
||||
return u, svc.recordAction(svc.ctx, uaProps, UserActionCreate, err)
|
||||
}
|
||||
|
||||
func (svc user) CreateWithAvatar(input *types.User, avatar io.Reader) (out *types.User, err error) {
|
||||
@ -303,40 +342,46 @@ func (svc user) CreateWithAvatar(input *types.User, avatar io.Reader) (out *type
|
||||
}
|
||||
|
||||
func (svc user) Update(upd *types.User) (u *types.User, err error) {
|
||||
if upd.ID == 0 {
|
||||
return nil, ErrInvalidID.withStack()
|
||||
}
|
||||
var (
|
||||
uaProps = &userActionProps{update: upd}
|
||||
)
|
||||
|
||||
if !handle.IsValid(upd.Handle) {
|
||||
return nil, ErrInvalidHandle.withStack()
|
||||
}
|
||||
|
||||
if _, err := mail.ParseAddress(upd.Email); err != nil {
|
||||
return nil, ErrUserInvalidEmail.withStack()
|
||||
}
|
||||
|
||||
if u, err = svc.user.FindByID(upd.ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if upd.ID != internalAuth.GetIdentityFromContext(svc.ctx).Identity() {
|
||||
if !svc.ac.CanUpdateUser(svc.ctx, u) {
|
||||
return nil, ErrNoUpdatePermissions.withStack()
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if upd.ID == 0 {
|
||||
return ErrInvalidID
|
||||
}
|
||||
}
|
||||
|
||||
// Assign changed values
|
||||
u.Email = upd.Email
|
||||
u.Username = upd.Username
|
||||
u.Name = upd.Name
|
||||
u.Handle = upd.Handle
|
||||
u.Kind = upd.Kind
|
||||
if !handle.IsValid(upd.Handle) {
|
||||
return UserErrInvalidHandle()
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.UserBeforeUpdate(upd, u)); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err := mail.ParseAddress(upd.Email); err != nil {
|
||||
return UserErrInvalidEmail()
|
||||
}
|
||||
|
||||
if u, err = svc.user.FindByID(upd.ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uaProps.setUser(u)
|
||||
|
||||
if upd.ID != internalAuth.GetIdentityFromContext(svc.ctx).Identity() {
|
||||
if !svc.ac.CanUpdateUser(svc.ctx, u) {
|
||||
return UserErrNotAllowedToUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
// Assign changed values
|
||||
u.Email = upd.Email
|
||||
u.Username = upd.Username
|
||||
u.Name = upd.Name
|
||||
u.Handle = upd.Handle
|
||||
u.Kind = upd.Kind
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.UserBeforeUpdate(upd, u)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return u, svc.db.Transaction(func() (err error) {
|
||||
if err = svc.UniqueCheck(u); err != nil {
|
||||
return
|
||||
}
|
||||
@ -348,8 +393,11 @@ func (svc user) Update(upd *types.User) (u *types.User, err error) {
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.UserAfterUpdate(upd, u))
|
||||
return
|
||||
})
|
||||
|
||||
return u, svc.recordAction(svc.ctx, uaProps, UserActionUpdate, err)
|
||||
}
|
||||
|
||||
// UniqueCheck verifies user's email, username and handle
|
||||
func (svc user) UniqueCheck(u *types.User) (err error) {
|
||||
isUnique := func(field string) bool {
|
||||
f := types.UserFilter{
|
||||
@ -392,15 +440,15 @@ func (svc user) UniqueCheck(u *types.User) (err error) {
|
||||
}
|
||||
|
||||
if !isUnique("email") {
|
||||
return ErrUserEmailNotUnique
|
||||
return UserErrEmailNotUnique()
|
||||
}
|
||||
|
||||
if !isUnique("username") {
|
||||
return ErrUserUsernameNotUnique
|
||||
return UserErrUsernameNotUnique()
|
||||
}
|
||||
|
||||
if !isUnique("handle") {
|
||||
return ErrUserHandleNotUnique
|
||||
return UserErrHandleNotUnique()
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -411,128 +459,173 @@ func (svc user) UpdateWithAvatar(mod *types.User, avatar io.Reader) (out *types.
|
||||
return svc.Create(mod)
|
||||
}
|
||||
|
||||
func (svc user) Delete(ID uint64) (err error) {
|
||||
func (svc user) Delete(userID uint64) (err error) {
|
||||
var (
|
||||
del *types.User
|
||||
u *types.User
|
||||
uaProps = &userActionProps{user: &types.User{ID: userID}}
|
||||
)
|
||||
|
||||
if ID == 0 {
|
||||
return ErrInvalidID.withStack()
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if userID == 0 {
|
||||
return UserErrInvalidID()
|
||||
}
|
||||
|
||||
if del, err = svc.user.FindByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
if u, err = svc.user.FindByID(userID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.ac.CanDeleteUser(svc.ctx, del) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
if !svc.ac.CanDeleteUser(svc.ctx, u) {
|
||||
return UserErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.UserBeforeUpdate(nil, del)); err != nil {
|
||||
return
|
||||
}
|
||||
if err = svc.eventbus.WaitFor(svc.ctx, event.UserBeforeUpdate(nil, u)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.user.DeleteByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
if err = svc.user.DeleteByID(userID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.UserAfterDelete(nil, del))
|
||||
return
|
||||
defer svc.eventbus.Dispatch(svc.ctx, event.UserAfterDelete(nil, u))
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, uaProps, UserActionDelete, err)
|
||||
}
|
||||
|
||||
func (svc user) Undelete(ID uint64) (err error) {
|
||||
if ID == 0 {
|
||||
return ErrInvalidID.withStack()
|
||||
}
|
||||
func (svc user) Undelete(userID uint64) (err error) {
|
||||
var (
|
||||
u *types.User
|
||||
uaProps = &userActionProps{user: &types.User{ID: userID}}
|
||||
)
|
||||
|
||||
var u *types.User
|
||||
if u, err = svc.user.FindByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if userID == 0 {
|
||||
return UserErrInvalidID()
|
||||
}
|
||||
|
||||
if err = svc.UniqueCheck(u); err != nil {
|
||||
return err
|
||||
}
|
||||
if u, err = svc.user.FindByID(userID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.ac.CanDeleteUser(svc.ctx, u) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
uaProps.setUser(u)
|
||||
|
||||
return svc.db.Transaction(func() (err error) {
|
||||
return svc.user.UndeleteByID(ID)
|
||||
if err = svc.UniqueCheck(u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !svc.ac.CanDeleteUser(svc.ctx, u) {
|
||||
return UserErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if err = svc.user.UndeleteByID(userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, uaProps, UserActionUndelete, err)
|
||||
|
||||
}
|
||||
|
||||
func (svc user) Suspend(ID uint64) (err error) {
|
||||
if ID == 0 {
|
||||
return ErrInvalidID.withStack()
|
||||
}
|
||||
func (svc user) Suspend(userID uint64) (err error) {
|
||||
var (
|
||||
u *types.User
|
||||
uaProps = &userActionProps{user: &types.User{ID: userID}}
|
||||
)
|
||||
|
||||
var u *types.User
|
||||
if u, err = svc.user.FindByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if userID == 0 {
|
||||
return UserErrInvalidID()
|
||||
}
|
||||
|
||||
if !svc.ac.CanSuspendUser(svc.ctx, u) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
if u, err = svc.user.FindByID(userID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return svc.db.Transaction(func() (err error) {
|
||||
return svc.user.SuspendByID(ID)
|
||||
uaProps.setUser(u)
|
||||
|
||||
if !svc.ac.CanSuspendUser(svc.ctx, u) {
|
||||
return UserErrNotAllowedToSuspend()
|
||||
}
|
||||
|
||||
if err = svc.user.SuspendByID(userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, uaProps, UserActionSuspend, err)
|
||||
|
||||
}
|
||||
|
||||
func (svc user) Unsuspend(ID uint64) (err error) {
|
||||
if ID == 0 {
|
||||
return ErrInvalidID.withStack()
|
||||
}
|
||||
func (svc user) Unsuspend(userID uint64) (err error) {
|
||||
var (
|
||||
u *types.User
|
||||
uaProps = &userActionProps{user: &types.User{ID: userID}}
|
||||
)
|
||||
|
||||
var u *types.User
|
||||
if u, err = svc.user.FindByID(ID); err != nil {
|
||||
return
|
||||
}
|
||||
err = svc.db.Transaction(func() (err error) {
|
||||
if userID == 0 {
|
||||
return UserErrInvalidID()
|
||||
}
|
||||
|
||||
if !svc.ac.CanUnsuspendUser(svc.ctx, u) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
if u, err = svc.user.FindByID(userID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return svc.db.Transaction(func() (err error) {
|
||||
return svc.user.UnsuspendByID(ID)
|
||||
uaProps.setUser(u)
|
||||
|
||||
if !svc.ac.CanUnsuspendUser(svc.ctx, u) {
|
||||
return UserErrNotAllowedToUnsuspend()
|
||||
}
|
||||
|
||||
if err = svc.user.UnsuspendByID(userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, uaProps, UserActionUnsuspend, err)
|
||||
|
||||
}
|
||||
|
||||
// SetPassword sets new password for a user
|
||||
//
|
||||
// Expecting setter to have permissions to update modify users and internal authentication enabled
|
||||
func (svc user) SetPassword(userID uint64, newPassword string) (err error) {
|
||||
log := svc.log(svc.ctx, zap.Uint64("userID", userID))
|
||||
var (
|
||||
u *types.User
|
||||
uaProps = &userActionProps{user: &types.User{ID: userID}}
|
||||
)
|
||||
|
||||
if !svc.settings.Auth.Internal.Enabled {
|
||||
return errors.New("internal authentication disabled")
|
||||
}
|
||||
err = svc.db.Transaction(func() error {
|
||||
if u, err = svc.user.FindByID(userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var u *types.User
|
||||
if u, err = svc.user.FindByID(userID); err != nil {
|
||||
return
|
||||
}
|
||||
uaProps.setUser(u)
|
||||
|
||||
if !svc.ac.CanUpdateUser(svc.ctx, u) {
|
||||
return ErrNoPermissions.withStack()
|
||||
}
|
||||
if !svc.ac.CanUpdateUser(svc.ctx, u) {
|
||||
return UserErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
if err = svc.auth.checkPasswordStrength(newPassword); err != nil {
|
||||
return
|
||||
}
|
||||
if !svc.auth.checkPasswordStrength(newPassword) {
|
||||
return UserErrPasswordNotSecure()
|
||||
}
|
||||
|
||||
return svc.db.Transaction(func() error {
|
||||
if err := svc.auth.changePassword(userID, newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("password changed")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc.recordAction(svc.ctx, uaProps, UserActionSetPassword, err)
|
||||
|
||||
}
|
||||
|
||||
// Masks (or leaves as-is) private data on user
|
||||
@ -546,6 +639,56 @@ func (svc user) handlePrivateData(u *types.User) {
|
||||
}
|
||||
}
|
||||
|
||||
// Preloader collects all ids of users, loads them and sets them back
|
||||
//
|
||||
//
|
||||
// @todo this kind of preloader is useful and can be implemented in bunch
|
||||
// of places and replace old code
|
||||
func (svc user) Preloader(g userIdGetter, f types.UserFilter, s userSetter) error {
|
||||
var (
|
||||
// channel that will collect the IDs in the getter
|
||||
ch = make(chan uint64, 0)
|
||||
|
||||
// unique index for IDs
|
||||
unq = make(map[uint64]bool)
|
||||
)
|
||||
|
||||
// Reset the collection of the IDs
|
||||
f.UserID = make([]uint64, 0)
|
||||
|
||||
// Call getter and collect the IDs
|
||||
go g(ch)
|
||||
|
||||
rangeLoop:
|
||||
for {
|
||||
select {
|
||||
case <-svc.ctx.Done():
|
||||
close(ch)
|
||||
break rangeLoop
|
||||
case id, ok := <-ch:
|
||||
if !ok {
|
||||
// Channel closed
|
||||
break rangeLoop
|
||||
}
|
||||
|
||||
if !unq[id] {
|
||||
unq[id] = true
|
||||
f.UserID = append(f.UserID, id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load all users (even if deleted, suspended) from the given list of IDs
|
||||
uu, _, err := svc.Find(f)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return uu.Walk(s)
|
||||
}
|
||||
|
||||
func createHandle(r repository.UserRepository, u *types.User) {
|
||||
if u.Handle == "" {
|
||||
u.Handle, _ = handle.Cast(
|
||||
|
||||
1139
system/service/user_actions.gen.go
Normal file
1139
system/service/user_actions.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
129
system/service/user_actions.yaml
Normal file
129
system/service/user_actions.yaml
Normal file
@ -0,0 +1,129 @@
|
||||
# List of loggable service actions
|
||||
|
||||
resource: system:user
|
||||
service: user
|
||||
|
||||
# Default sensitivity for actions
|
||||
defaultActionSeverity: notice
|
||||
|
||||
# default severity for errors
|
||||
defaultErrorSeverity: alert
|
||||
|
||||
import:
|
||||
- github.com/cortezaproject/corteza-server/system/types
|
||||
|
||||
props:
|
||||
- name: user
|
||||
type: "*types.User"
|
||||
fields: [ handle, email, name, username, ID ]
|
||||
- name: new
|
||||
type: "*types.User"
|
||||
fields: [ handle, email, name, username, ID ]
|
||||
- name: update
|
||||
type: "*types.User"
|
||||
fields: [ handle, email, name, username, ID ]
|
||||
- name: existing
|
||||
type: "*types.User"
|
||||
fields: [ handle, email, name, username, ID ]
|
||||
- name: filter
|
||||
type: "*types.UserFilter"
|
||||
fields: [ query, userID, roleID, handle, email, username, deleted, suspended, sort ]
|
||||
|
||||
actions:
|
||||
- action: search
|
||||
log: "searched for matching users"
|
||||
severity: info
|
||||
|
||||
- action: lookup
|
||||
log: "looked-up for a {user}"
|
||||
severity: info
|
||||
|
||||
- action: create
|
||||
log: "created {user}"
|
||||
|
||||
- action: update
|
||||
log: "updated {user}"
|
||||
|
||||
- action: delete
|
||||
log: "deleted {user}"
|
||||
|
||||
- action: undelete
|
||||
log: "undeleted {user}"
|
||||
|
||||
- action: suspend
|
||||
log: "suspended {user}"
|
||||
|
||||
- action: unsuspend
|
||||
log: "unsuspended {user}"
|
||||
|
||||
- action: setPassword
|
||||
log: "password changed for {user}"
|
||||
|
||||
errors:
|
||||
- error: nonexistent
|
||||
message: "user does not exist"
|
||||
severity: warning
|
||||
|
||||
- error: invalidID
|
||||
message: "invalid ID"
|
||||
severity: warning
|
||||
|
||||
- error: invalidHandle
|
||||
message: "invalid handle"
|
||||
severity: warning
|
||||
|
||||
- error: invalidEmail
|
||||
message: "invalid email"
|
||||
severity: warning
|
||||
|
||||
|
||||
- error: notAllowedToRead
|
||||
message: "not allowed to read user"
|
||||
log: "failed to read {user.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToListUsers
|
||||
message: "not allowed to list users"
|
||||
log: "failed to list user; insufficient permissions"
|
||||
|
||||
- error: notAllowedToCreate
|
||||
message: "not allowed to create user"
|
||||
log: "failed to create user; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUpdate
|
||||
message: "not allowed to update user"
|
||||
log: "failed to update {user.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToDelete
|
||||
message: "not allowed to delete user"
|
||||
log: "failed to delete {user.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUndelete
|
||||
message: "not allowed to undelete user"
|
||||
log: "failed to undelete {user.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToSuspend
|
||||
message: "not allowed to suspend user"
|
||||
log: "failed to suspend {user.handle}; insufficient permissions"
|
||||
|
||||
- error: notAllowedToUnsuspend
|
||||
message: "not allowed to unsuspend user"
|
||||
log: "failed to unsuspend {user.handle}; insufficient permissions"
|
||||
|
||||
- error: handleNotUnique
|
||||
message: "handle not unique"
|
||||
log: "used duplicate handle ({user.handle}) for user"
|
||||
severity: warning
|
||||
|
||||
- error: emailNotUnique
|
||||
message: "email not unique"
|
||||
log: "used duplicate email ({user.email}) for user"
|
||||
severity: warning
|
||||
|
||||
- error: usernameNotUnique
|
||||
message: "username not unique"
|
||||
log: "used duplicate username ({user.username}) for user"
|
||||
severity: warning
|
||||
|
||||
- error: passwordNotSecure
|
||||
message: "provided password is not secure; use longer password with more non-alphanumeric character"
|
||||
|
||||
@ -68,7 +68,7 @@ func TestRoleList(t *testing.T) {
|
||||
End()
|
||||
}
|
||||
|
||||
func TestRoleList_filterForbiden(t *testing.T) {
|
||||
func TestRoleList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
|
||||
// @todo this can be a problematic test because it leaves
|
||||
@ -98,7 +98,7 @@ func TestRoleCreateForbidden(t *testing.T) {
|
||||
FormData("name", rs()).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("system.service.NoCreatePermissions")).
|
||||
Assert(helpers.AssertError("not allowed to create role")).
|
||||
End()
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ func TestRoleCreateNotUnique(t *testing.T) {
|
||||
FormData("handle", role.Handle).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("system.service.RoleHandleNotUnique")).
|
||||
Assert(helpers.AssertError("role handle not unique")).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
@ -122,7 +122,7 @@ func TestRoleCreateNotUnique(t *testing.T) {
|
||||
FormData("handle", "handle_"+rs()).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("system.service.RoleNameNotUnique")).
|
||||
Assert(helpers.AssertError("role name not unique")).
|
||||
End()
|
||||
|
||||
}
|
||||
@ -150,7 +150,7 @@ func TestRoleUpdateForbidden(t *testing.T) {
|
||||
FormData("email", h.randEmail()).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("system.service.NoUpdatePermissions")).
|
||||
Assert(helpers.AssertError("not allowed to update role")).
|
||||
End()
|
||||
}
|
||||
|
||||
@ -186,7 +186,7 @@ func TestRoleDeleteForbidden(t *testing.T) {
|
||||
Delete(fmt.Sprintf("/roles/%d", u.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("system.service.NoPermissions")).
|
||||
Assert(helpers.AssertError("not allowed to delete role")).
|
||||
End()
|
||||
}
|
||||
|
||||
|
||||
@ -245,7 +245,7 @@ func TestUserCreateForbidden(t *testing.T) {
|
||||
FormData("email", h.randEmail()).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("system.service.NoCreatePermissions")).
|
||||
Assert(helpers.AssertError("not allowed to create user")).
|
||||
End()
|
||||
}
|
||||
|
||||
@ -278,7 +278,7 @@ func TestUserUpdateForbidden(t *testing.T) {
|
||||
FormData("email", h.randEmail()).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("system.service.NoUpdatePermissions")).
|
||||
Assert(helpers.AssertError("not allowed to update user")).
|
||||
End()
|
||||
}
|
||||
|
||||
@ -311,7 +311,7 @@ func TestUserDeleteForbidden(t *testing.T) {
|
||||
Delete(fmt.Sprintf("/users/%d", u.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("system.service.NoPermissions")).
|
||||
Assert(helpers.AssertError("not allowed to delete user")).
|
||||
End()
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user