Commit f7837166 authored by ale's avatar ale

Refactor authentication and request handling into a single entry point

Simplifies the code in AccountService a bit, moving more logic into
the request types. The authentication and authorization bits have been
split into a separate authService type for clarity.
parent 2fd20609
......@@ -87,9 +87,12 @@ type GetUserRequest struct {
}
// GetUser returns public information about a user.
func (s *AccountService) GetUser(ctx context.Context, tx TX, req *GetUserRequest) (*User, error) {
_, user, err := s.authorizeUser(req.NewContext(ctx), tx, req.RequestBase)
return user, err
func (s *AccountService) GetUser(ctx context.Context, tx TX, req *GetUserRequest) (resp *User, err error) {
err = s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) error {
resp = user
return nil
})
return
}
// setResourceStatus sets the status of a single resource (shared
......@@ -110,11 +113,7 @@ type DisableResourceRequest struct {
// DisableResource disables a resource belonging to the user.
func (s *AccountService) DisableResource(ctx context.Context, tx TX, req *DisableResourceRequest) error {
ctx, r, err := s.authorizeResource(ctx, tx, req.ResourceRequestBase)
if err != nil {
return err
}
return s.withRequest(ctx, req, nil, func(ctx context.Context) error {
return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
return s.setResourceStatus(ctx, tx, r, ResourceStatusInactive)
})
}
......@@ -126,11 +125,7 @@ type EnableResourceRequest struct {
// EnableResource enables a resource belonging to the user.
func (s *AccountService) EnableResource(ctx context.Context, tx TX, req *EnableResourceRequest) error {
ctx, r, err := s.authorizeResource(ctx, tx, req.ResourceRequestBase)
if err != nil {
return err
}
return s.withRequest(ctx, req, nil, func(ctx context.Context) error {
return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
return s.setResourceStatus(ctx, tx, r, ResourceStatusActive)
})
}
......@@ -149,12 +144,7 @@ func (r *ChangeUserPasswordRequest) Validate(ctx context.Context, s *AccountServ
// ChangeUserPassword updates a user's password. It will also take
// care of re-encrypting the user encryption key, if present.
func (s *AccountService) ChangeUserPassword(ctx context.Context, tx TX, req *ChangeUserPasswordRequest) error {
ctx, user, err := s.authorizeUserWithPassword(ctx, tx, req.PrivilegedRequestBase)
if err != nil {
return err
}
return s.withRequest(ctx, req, user, func(ctx context.Context) error {
return s.handleUserRequest(ctx, tx, req, s.authUserWithPassword(req.PrivilegedRequestBase), func(ctx context.Context, user *User) error {
return s.changeUserPasswordAndUpdateEncryptionKeys(ctx, tx, user, req.CurPassword, req.Password)
})
}
......@@ -177,14 +167,14 @@ func (r *PasswordRecoveryRequest) Validate(ctx context.Context, s *AccountServic
//
// TODO: call out to auth-server for secondary authentication?
func (s *AccountService) RecoverPassword(ctx context.Context, tx TX, req *PasswordRecoveryRequest) error {
user, err := s.getUser(ctx, tx, req.Username)
user, err := getUserOrDie(ctx, tx, req.Username)
if err != nil {
return err
}
// TODO: authenticate with the secret recovery password.
ctx = context.WithValue(ctx, authUserCtxKey, req.Username)
return s.withRequest(ctx, req, user, func(ctx context.Context) error {
return s.withRequest(ctx, tx, req, user, func(ctx context.Context) error {
// Change the user password (the recovery password does not change).
return s.changeUserPasswordAndUpdateEncryptionKeys(ctx, tx, user, req.RecoveryPassword, req.Password)
})
......@@ -205,12 +195,7 @@ func (r *ResetPasswordRequest) Validate(ctx context.Context, s *AccountService)
// password for an account. The user will lose access to all stored
// email (because the encryption keys will be reset) and to 2FA.
func (s *AccountService) ResetPassword(ctx context.Context, tx TX, req *ResetPasswordRequest) error {
ctx, user, err := s.authorizeAdmin(ctx, tx, req.RequestBase)
if err != nil {
return err
}
return s.withRequest(ctx, req, user, func(ctx context.Context) error {
return s.handleUserRequest(ctx, tx, req, s.authAdmin(req.RequestBase), func(ctx context.Context, user *User) error {
// Disable 2FA.
if err := s.disable2FA(ctx, tx, user); err != nil {
return err
......@@ -237,12 +222,7 @@ func (r *SetPasswordRecoveryHintRequest) Validate(ctx context.Context, s *Accoun
// SetPasswordRecoveryHint lets users set the password recovery hint
// and expected response (secondary password).
func (s *AccountService) SetPasswordRecoveryHint(ctx context.Context, tx TX, req *SetPasswordRecoveryHintRequest) error {
ctx, user, err := s.authorizeUserWithPassword(ctx, tx, req.PrivilegedRequestBase)
if err != nil {
return err
}
return s.withRequest(ctx, req, user, func(ctx context.Context) error {
return s.handleUserRequest(ctx, tx, req, s.authUserWithPassword(req.PrivilegedRequestBase), func(ctx context.Context, user *User) error {
// If the encryption keys are not set up yet, use the
// CurPassword to initialize them.
keys, decrypted, err := s.readOrInitializeEncryptionKeys(ctx, tx, user, req.CurPassword, req.CurPassword)
......@@ -361,13 +341,8 @@ type CreateApplicationSpecificPasswordResponse struct {
// CreateApplicationSpecificPassword will generate a new
// application-specific password for the given service.
func (s *AccountService) CreateApplicationSpecificPassword(ctx context.Context, tx TX, req *CreateApplicationSpecificPasswordRequest) (*CreateApplicationSpecificPasswordResponse, error) {
ctx, user, err := s.authorizeUserWithPassword(ctx, tx, req.PrivilegedRequestBase)
if err != nil {
return nil, err
}
var resp CreateApplicationSpecificPasswordResponse
err = s.withRequest(ctx, req, user, func(ctx context.Context) error {
err := s.handleUserRequest(ctx, tx, req, s.authUserWithPassword(req.PrivilegedRequestBase), func(ctx context.Context, user *User) error {
// No application-specific passwords unless 2FA is enabled.
if !user.Has2FA {
return newRequestError(errors.New("2FA is not enabled for this user"))
......@@ -421,13 +396,8 @@ type DeleteApplicationSpecificPasswordRequest struct {
// DeleteApplicationSpecificPassword destroys an application-specific
// password, identified by its unique ID.
func (s *AccountService) DeleteApplicationSpecificPassword(ctx context.Context, tx TX, req *DeleteApplicationSpecificPasswordRequest) error {
ctx, user, err := s.authorizeUser(ctx, tx, req.RequestBase)
if err != nil {
return err
}
return s.withRequest(ctx, req, user, func(ctx context.Context) error {
if err = tx.DeleteApplicationSpecificPassword(ctx, user, req.AspID); err != nil {
return s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) error {
if err := tx.DeleteApplicationSpecificPassword(ctx, user, req.AspID); err != nil {
return err
}
......@@ -468,14 +438,8 @@ type MoveResourceResponse struct {
// once regardless of which individual ResourceID is provided as long
// as it belongs to the group.
func (s *AccountService) MoveResource(ctx context.Context, tx TX, req *MoveResourceRequest) (*MoveResourceResponse, error) {
ctx, user, err := s.authorizeAdmin(ctx, tx, req.RequestBase)
if err != nil {
return nil, err
}
var resp MoveResourceResponse
err = s.withRequest(ctx, req, user, func(ctx context.Context) error {
err := s.handleUserRequest(ctx, tx, req, s.authAdmin(req.RequestBase), func(ctx context.Context, user *User) error {
// Collect all related resources, as they should all be moved at once.
r, err := tx.GetResource(ctx, req.ResourceID)
if err != nil {
......@@ -526,13 +490,8 @@ type EnableOTPResponse struct {
// or it can let the server generate a new secret by passing an empty
// totp_secret.
func (s *AccountService) EnableOTP(ctx context.Context, tx TX, req *EnableOTPRequest) (*EnableOTPResponse, error) {
ctx, user, err := s.authorizeUser(ctx, tx, req.RequestBase)
if err != nil {
return nil, err
}
var resp EnableOTPResponse
err = s.withRequest(ctx, req, user, func(ctx context.Context) error {
err := s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) (err error) {
// Replace or initialize the TOTP secret.
if req.TOTPSecret == "" {
req.TOTPSecret, err = generateTOTPSecret()
......@@ -558,13 +517,8 @@ type DisableOTPRequest struct {
// DisableOTP disables two-factor authentication for a user.
func (s *AccountService) DisableOTP(ctx context.Context, tx TX, req *DisableOTPRequest) error {
ctx, user, err := s.authorizeUser(ctx, tx, req.RequestBase)
if err != nil {
return err
}
// Delete the TOTP secret (if present).
return s.withRequest(ctx, req, user, func(ctx context.Context) error {
return s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) error {
// Delete the TOTP secret (if present).
if err := tx.DeleteUserTOTPSecret(ctx, user); err != nil {
return newBackendError(err)
}
......@@ -590,12 +544,7 @@ const maxEmailAliases = 5
// AddEmailAlias adds an alias (additional address) to an email resource.
func (s *AccountService) AddEmailAlias(ctx context.Context, tx TX, req *AddEmailAliasRequest) error {
ctx, r, err := s.authorizeResource(ctx, tx, req.ResourceRequestBase)
if err != nil {
return err
}
return s.withRequest(ctx, req, nil, func(ctx context.Context) error {
return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
// Allow at most 5 aliases.
if len(r.Email.Aliases) >= maxEmailAliases {
return errors.New("too many aliases")
......@@ -627,12 +576,7 @@ func (r *DeleteEmailAliasRequest) Validate(ctx context.Context, s *AccountServic
// DeleteEmailAlias removes an alias from an email resource.
func (s *AccountService) DeleteEmailAlias(ctx context.Context, tx TX, req *DeleteEmailAliasRequest) error {
ctx, r, err := s.authorizeResource(ctx, tx, req.ResourceRequestBase)
if err != nil {
return err
}
return s.withRequest(ctx, req, nil, func(ctx context.Context) error {
return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
var aliases []string
for _, a := range r.Email.Aliases {
if a != req.Addr {
......@@ -669,6 +613,28 @@ func (req *CreateResourcesRequest) ApplyTemplate(ctx context.Context, s *Account
}
}
// ValidationUser returns the user to be used for validation purposes.
func (req *CreateResourcesRequest) ValidationUser(ctx context.Context, tx TX) *User {
// Fetch the user associated with the first resource (if
// any). Since resource validation might reference other
// resources, we need to provide it with a view of what the
// future resources will be. So we merge the resources from
// the database with those from the request, using a local
// copy of the User object.
if len(req.Resources) > 0 {
if username := req.Resources[0].ID.User(); username != "" {
u, err := getUserOrDie(ctx, tx, username)
if err != nil {
return nil
}
user := *u
user.Resources = mergeResources(u.Resources, req.Resources)
return &user
}
}
return nil
}
// Validate the request.
func (req *CreateResourcesRequest) Validate(ctx context.Context, s *AccountService, user *User) error {
var owner string
......@@ -692,32 +658,8 @@ func (req *CreateResourcesRequest) Validate(ctx context.Context, s *AccountServi
// CreateResources can create one or more resources.
func (s *AccountService) CreateResources(ctx context.Context, tx TX, req *CreateResourcesRequest) (*CreateResourcesResponse, error) {
ctx, err := s.authorizeAdminGeneric(ctx, tx, req.SSO)
if err != nil {
return nil, err
}
// Fetch the user associated with the first resource (if
// any). Since resource validation might reference other
// resources, we need to provide it with a view of what the
// future resources will be. So we merge the resources from
// the database with those from the request, using a local
// copy of the User object.
var user *User
if len(req.Resources) > 0 {
if username := req.Resources[0].ID.User(); username != "" {
u, err := tx.GetUser(ctx, username)
if err != nil {
return nil, err
}
tmp := *u
tmp.Resources = mergeResources(u.Resources, req.Resources)
user = &tmp
}
}
var resp CreateResourcesResponse
err = s.withRequest(ctx, req, user, func(ctx context.Context) error {
err := s.handleAdminRequest(ctx, tx, req, req.SSO, func(ctx context.Context) error {
for _, r := range req.Resources {
if err := tx.CreateResource(ctx, r); err != nil {
return err
......@@ -758,7 +700,7 @@ func (req *CreateUserRequest) ApplyTemplate(ctx context.Context, s *AccountServi
}
// Validate the request.
func (req *CreateUserRequest) Validate(ctx context.Context, s *AccountService, user *User) error {
func (req *CreateUserRequest) Validate(ctx context.Context, s *AccountService, _ *User) error {
// Override server-generated values.
fillUserTemplate(req.User)
......@@ -798,13 +740,8 @@ func fillUserTemplate(user *User) {
// CreateUser creates a new user along with the associated resources.
func (s *AccountService) CreateUser(ctx context.Context, tx TX, req *CreateUserRequest) (*CreateUserResponse, error) {
ctx, err := s.authorizeAdminGeneric(ctx, tx, req.SSO)
if err != nil {
return nil, err
}
var resp CreateUserResponse
err = s.withRequest(ctx, req, req.User, func(ctx context.Context) error {
err := s.handleAdminRequest(ctx, tx, req, req.SSO, func(ctx context.Context) (err error) {
if err := tx.CreateUser(ctx, req.User); err != nil {
return err
}
......
......@@ -301,7 +301,7 @@ func TestService_EncryptionKeys(t *testing.T) {
tx, _ := fb.NewTransaction()
ctx := context.Background()
user, _ := svc.getUser(ctx, tx, "testuser")
user, _ := getUserOrDie(ctx, tx, "testuser")
// Set the keys to something.
keys, _, err := svc.initializeEncryptionKeys(ctx, tx, user, "password")
......
......@@ -64,10 +64,7 @@ type FindResourceRequest struct {
// AccountService implements the business logic and high-level
// functionality of the user accounts management service.
type AccountService struct {
validator sso.Validator
ssoService string
ssoGroups []string
ssoAdminGroup string
*authService
audit auditLogger
......@@ -89,11 +86,8 @@ func NewAccountService(backend Backend, config *Config) (*AccountService, error)
func newAccountServiceWithSSO(backend Backend, config *Config, ssoValidator sso.Validator) (*AccountService, error) {
s := &AccountService{
validator: ssoValidator,
ssoService: config.SSO.Service,
ssoGroups: config.SSO.Groups,
ssoAdminGroup: config.SSO.AdminGroup,
audit: &syslogAuditLogger{},
authService: newAuthService(config, ssoValidator),
audit: &syslogAuditLogger{},
}
vc, err := config.validationContext(backend)
......@@ -109,7 +103,23 @@ func newAccountServiceWithSSO(backend Backend, config *Config, ssoValidator sso.
return s, nil
}
func (s *AccountService) isAdmin(tkt *sso.Ticket) bool {
type authService struct {
validator sso.Validator
ssoService string
ssoGroups []string
ssoAdminGroup string
}
func newAuthService(config *Config, v sso.Validator) *authService {
return &authService{
validator: v,
ssoService: config.SSO.Service,
ssoGroups: config.SSO.Groups,
ssoAdminGroup: config.SSO.AdminGroup,
}
}
func (s *authService) isAdmin(tkt *sso.Ticket) bool {
for _, g := range tkt.Groups {
if g == s.ssoAdminGroup {
return true
......@@ -118,11 +128,11 @@ func (s *AccountService) isAdmin(tkt *sso.Ticket) bool {
return false
}
func (s *AccountService) validateSSO(ssoToken string) (*sso.Ticket, error) {
func (s *authService) validateSSO(ssoToken string) (*sso.Ticket, error) {
return s.validator.Validate(ssoToken, "", s.ssoService, s.ssoGroups)
}
func (s *AccountService) getUser(ctx context.Context, tx TX, username string) (*User, error) {
func getUserOrDie(ctx context.Context, tx TX, username string) (*User, error) {
user, err := tx.GetUser(ctx, username)
if err != nil {
return nil, newBackendError(err)
......@@ -133,7 +143,7 @@ func (s *AccountService) getUser(ctx context.Context, tx TX, username string) (*
return user, nil
}
func (s *AccountService) getResource(ctx context.Context, tx TX, id ResourceID) (*Resource, error) {
func getResourceOrDie(ctx context.Context, tx TX, id ResourceID) (*Resource, error) {
r, err := tx.GetResource(ctx, id)
if err != nil {
return nil, newBackendError(err)
......@@ -156,7 +166,7 @@ func authUserFromContext(ctx context.Context) string {
return ""
}
func (s *AccountService) authorizeAdminGeneric(ctx context.Context, tx TX, ssoTicket string) (context.Context, error) {
func (s *authService) authorizeAdminGeneric(ctx context.Context, tx TX, ssoTicket string) (context.Context, error) {
// Validate the SSO ticket.
tkt, err := s.validateSSO(ssoTicket)
if err != nil {
......@@ -173,7 +183,7 @@ func (s *AccountService) authorizeAdminGeneric(ctx context.Context, tx TX, ssoTi
return ctx, nil
}
func (s *AccountService) authorizeAdmin(ctx context.Context, tx TX, req RequestBase) (context.Context, *User, error) {
func (s *authService) authorizeAdmin(ctx context.Context, tx TX, req RequestBase) (context.Context, *User, error) {
// Validate the SSO ticket.
tkt, err := s.validateSSO(req.SSO)
if err != nil {
......@@ -187,11 +197,11 @@ func (s *AccountService) authorizeAdmin(ctx context.Context, tx TX, req RequestB
}
ctx = context.WithValue(ctx, authUserCtxKey, tkt.User)
user, err := s.getUser(ctx, tx, req.Username)
user, err := getUserOrDie(ctx, tx, req.Username)
return ctx, user, err
}
func (s *AccountService) authorizeUser(ctx context.Context, tx TX, req RequestBase) (context.Context, *User, error) {
func (s *authService) authorizeUser(ctx context.Context, tx TX, req RequestBase) (context.Context, *User, error) {
// First, check that the username matches the SSO ticket
// username (or that the SSO ticket has admin permissions).
tkt, err := s.validateSSO(req.SSO)
......@@ -205,7 +215,7 @@ func (s *AccountService) authorizeUser(ctx context.Context, tx TX, req RequestBa
return nil, nil, newAuthError(ErrUnauthorized)
}
user, err := s.getUser(ctx, tx, req.Username)
user, err := getUserOrDie(ctx, tx, req.Username)
ctx = context.WithValue(ctx, authUserCtxKey, tkt.User)
return ctx, user, err
}
......@@ -213,7 +223,7 @@ func (s *AccountService) authorizeUser(ctx context.Context, tx TX, req RequestBa
// Extended version of authorizeUser that also directly checks the
// user password. Used for account-privileged operations related to
// credential manipulation.
func (s *AccountService) authorizeUserWithPassword(ctx context.Context, tx TX, req PrivilegedRequestBase) (context.Context, *User, error) {
func (s *authService) authorizeUserWithPassword(ctx context.Context, tx TX, req PrivilegedRequestBase) (context.Context, *User, error) {
// TODO: call out to the auth-server?
return s.authorizeUser(ctx, tx, req.RequestBase)
}
......@@ -226,13 +236,13 @@ func (s *AccountService) authorizeUserWithPassword(ctx context.Context, tx TX, r
// have explicit ownership (i.e. they have the user in their resource
// ID). For shared resources like mailing lists, we will need to
// delegate the ownership check to the Resource itself.
func (s *AccountService) authorizeResource(ctx context.Context, tx TX, req ResourceRequestBase) (context.Context, *Resource, error) {
func (s *authService) authorizeResource(ctx context.Context, tx TX, req ResourceRequestBase) (context.Context, *Resource, error) {
tkt, err := s.validateSSO(req.SSO)
if err != nil {
return nil, nil, newAuthError(err)
}
r, err := s.getResource(ctx, tx, req.ResourceID)
r, err := getResourceOrDie(ctx, tx, req.ResourceID)
if err != nil {
return nil, nil, err
}
......@@ -260,6 +270,36 @@ func canAccessResource(username string, r *Resource) bool {
}
}
type authHandlerFunc func(context.Context, TX) (context.Context, *User, *Resource, error)
func (s *authService) authResource(reqBase ResourceRequestBase) authHandlerFunc {
return func(ctx context.Context, tx TX) (context.Context, *User, *Resource, error) {
ctx, resource, err := s.authorizeResource(ctx, tx, reqBase)
return ctx, nil, resource, err
}
}
func (s *authService) authUser(reqBase RequestBase) authHandlerFunc {
return func(ctx context.Context, tx TX) (context.Context, *User, *Resource, error) {
ctx, user, err := s.authorizeUser(ctx, tx, reqBase)
return ctx, user, nil, err
}
}
func (s *authService) authUserWithPassword(reqBase PrivilegedRequestBase) authHandlerFunc {
return func(ctx context.Context, tx TX) (context.Context, *User, *Resource, error) {
ctx, user, err := s.authorizeUserWithPassword(ctx, tx, reqBase)
return ctx, user, nil, err
}
}
func (s *authService) authAdmin(reqBase RequestBase) authHandlerFunc {
return func(ctx context.Context, tx TX) (context.Context, *User, *Resource, error) {
ctx, user, err := s.authorizeAdmin(ctx, tx, reqBase)
return ctx, user, nil, err
}
}
type hasNewContext interface {
NewContext(context.Context) context.Context
}
......@@ -276,10 +316,15 @@ type hasCompoundValidate interface {
Validate(context.Context, *AccountService, *User) error
}
// Wrapper for actions that sets up some request-related parameters
// (mostly in the Context, used for later logging). The user
// parameter, if present, is passed to the Validate request method.
func (s *AccountService) withRequest(ctx context.Context, req interface{}, user *User, f func(context.Context) error) error {
type hasValidationUser interface {
ValidationUser(context.Context, TX) *User
}
// Wrapper for actions that validates requests and sets up some
// request-related parameters (mostly in the Context, used for later
// logging). The user parameter, if present, is passed to the Validate
// request method.
func (s *AccountService) withRequest(ctx context.Context, tx TX, req interface{}, user *User, f func(context.Context) error) error {
// If the request has a NewContext() method, call it to obtain
// a request-specific context (this step usually adds
// parameters for logging).
......@@ -287,6 +332,14 @@ func (s *AccountService) withRequest(ctx context.Context, req interface{}, user
ctx = rnc.NewContext(ctx)
}
// Fetch the user to be passed to Validate() from the request
// if one was not provided as an argument.
if user == nil {
if rvu, ok := req.(hasValidationUser); ok {
user = rvu.ValidationUser(ctx, tx)
}
}
// Apply a template to the request, to fill in default values
// etc., if the request has an ApplyTemplate() method.
if rt, ok := req.(hasApplyTemplate); ok {
......@@ -313,6 +366,43 @@ func (s *AccountService) withRequest(ctx context.Context, req interface{}, user
return err
}
// Handle a user request. Authorize, validate, and run the given function on the User object.
func (s *AccountService) handleUserRequest(ctx context.Context, tx TX, req interface{}, auth authHandlerFunc, f func(context.Context, *User) error) (err error) {
var user *User
ctx, user, _, err = auth(ctx, tx)
if err != nil {
return
}
return s.withRequest(ctx, tx, req, user, func(ctx context.Context) error {
return f(ctx, user)
})
}
// Handle a resource request. Authorize, validate, and run the given function on the Resource object.
func (s *AccountService) handleResourceRequest(ctx context.Context, tx TX, req interface{}, auth authHandlerFunc, f func(context.Context, *Resource) error) (err error) {
var user *User
var resource *Resource
ctx, user, resource, err = auth(ctx, tx)
if err != nil {
return
}
return s.withRequest(ctx, tx, req, user, func(ctx context.Context) error {
return f(ctx, resource)
})
}
// Handle an admin request (i.e. a request with no explicit associated account or resource).
func (s *AccountService) handleAdminRequest(ctx context.Context, tx TX, req interface{}, ssoTicket string, f func(context.Context) error) (err error) {
ctx, err = s.authService.authorizeAdminGeneric(ctx, tx, ssoTicket)
if err != nil {
return
}
return s.withRequest(ctx, tx, req, nil, f)
}
func dumpRequest(req interface{}) string {
data, _ := json.Marshal(req)
return string(data)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment