2020-08-06 12:50:20 -07:00
package authmethods
import (
"context"
"errors"
"fmt"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/auth/password/store"
"github.com/hashicorp/boundary/internal/db"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authmethods"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
2020-09-04 14:48:11 -07:00
"github.com/hashicorp/boundary/internal/servers/controller/common"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
2020-09-04 14:48:11 -07:00
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
2020-09-03 08:02:30 -07:00
"github.com/hashicorp/boundary/internal/types/scope"
2020-08-06 12:50:20 -07:00
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/wrapperspb"
)
var (
maskManager handlers . MaskManager
)
func init () {
var err error
2020-08-14 14:08:12 -07:00
if maskManager , err = handlers . NewMaskManager ( & store . AuthMethod {}, & pb . AuthMethod {}, & pb . PasswordAuthMethodAttributes {}); err != nil {
2020-08-06 12:50:20 -07:00
panic ( err )
}
}
// Service handles request as described by the pbs.AuthMethodServiceServer interface.
type Service struct {
2020-09-04 14:48:11 -07:00
repoFn common . PasswordAuthRepoFactory
iamRepoFn common . IamRepoFactory
2020-08-06 12:50:20 -07:00
}
2020-08-10 17:15:02 -04:00
// NewService returns a auth method service which handles auth method related requests to boundary.
2020-09-04 14:48:11 -07:00
func NewService ( repo common . PasswordAuthRepoFactory , iamRepoFn common . IamRepoFactory ) ( Service , error ) {
2020-08-06 12:50:20 -07:00
if repo == nil {
2020-09-04 14:48:11 -07:00
return Service {}, fmt . Errorf ( "nil password repository provided" )
}
if iamRepoFn == nil {
2020-08-06 12:50:20 -07:00
return Service {}, fmt . Errorf ( "nil iam repository provided" )
}
2020-09-04 14:48:11 -07:00
return Service { repoFn : repo , iamRepoFn : iamRepoFn }, nil
2020-08-06 12:50:20 -07:00
}
var _ pbs . AuthMethodServiceServer = Service {}
// ListAuthMethods implements the interface pbs.AuthMethodServiceServer.
func ( s Service ) ListAuthMethods ( ctx context . Context , req * pbs . ListAuthMethodsRequest ) ( * pbs . ListAuthMethodsResponse , error ) {
if err := validateListRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetScopeId (), action . List )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-08-06 12:50:20 -07:00
ul , err := s . listFromRepo ( ctx , authResults . Scope . GetId ())
if err != nil {
return nil , err
}
for _ , item := range ul {
item . Scope = authResults . Scope
}
return & pbs . ListAuthMethodsResponse { Items : ul }, nil
}
2020-08-21 08:15:32 -07:00
// GetAuthMethod implements the interface pbs.AuthMethodServiceServer.
2020-08-06 12:50:20 -07:00
func ( s Service ) GetAuthMethod ( ctx context . Context , req * pbs . GetAuthMethodRequest ) ( * pbs . GetAuthMethodResponse , error ) {
if err := validateGetRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . Read )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-08-06 12:50:20 -07:00
u , err := s . getFromRepo ( ctx , req . GetId ())
if err != nil {
return nil , err
}
u . Scope = authResults . Scope
return & pbs . GetAuthMethodResponse { Item : u }, nil
}
// CreateAuthMethod implements the interface pbs.AuthMethodServiceServer.
func ( s Service ) CreateAuthMethod ( ctx context . Context , req * pbs . CreateAuthMethodRequest ) ( * pbs . CreateAuthMethodResponse , error ) {
if err := validateCreateRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetItem (). GetScopeId (), action . Create )
if authResults . Error != nil {
return nil , authResults . Error
}
u , err := s . createInRepo ( ctx , authResults . Scope . GetId (), req . GetItem ())
2020-08-06 12:50:20 -07:00
if err != nil {
return nil , err
}
u . Scope = authResults . Scope
2020-09-02 19:43:41 -07:00
return & pbs . CreateAuthMethodResponse { Item : u , Uri : fmt . Sprintf ( "auth-methods/%s" , u . GetId ())}, nil
2020-08-06 12:50:20 -07:00
}
// UpdateAuthMethod implements the interface pbs.AuthMethodServiceServer.
func ( s Service ) UpdateAuthMethod ( ctx context . Context , req * pbs . UpdateAuthMethodRequest ) ( * pbs . UpdateAuthMethodResponse , error ) {
if err := validateUpdateRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . Update )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-08-18 17:38:38 -07:00
u , err := s . updateInRepo ( ctx , authResults . Scope . GetId (), req . GetId (), req . GetUpdateMask (). GetPaths (), req . GetItem ())
2020-08-06 12:50:20 -07:00
if err != nil {
return nil , err
}
2020-08-12 08:20:35 -07:00
u . Scope = authResults . Scope
2020-08-06 12:50:20 -07:00
return & pbs . UpdateAuthMethodResponse { Item : u }, nil
}
// DeleteAuthMethod implements the interface pbs.AuthMethodServiceServer.
func ( s Service ) DeleteAuthMethod ( ctx context . Context , req * pbs . DeleteAuthMethodRequest ) ( * pbs . DeleteAuthMethodResponse , error ) {
if err := validateDeleteRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
authResults := s . authResult ( ctx , req . GetId (), action . Delete )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-09-08 23:57:26 -04:00
_ , err := s . deleteFromRepo ( ctx , authResults . Scope . GetId (), req . GetId ())
2020-08-06 12:50:20 -07:00
if err != nil {
return nil , err
}
2020-09-08 23:57:26 -04:00
return nil , nil
2020-08-06 12:50:20 -07:00
}
func ( s Service ) getFromRepo ( ctx context . Context , id string ) ( * pb . AuthMethod , error ) {
repo , err := s . repoFn ()
if err != nil {
return nil , err
}
u , err := repo . LookupAuthMethod ( ctx , id )
if err != nil {
if errors . Is ( err , db . ErrRecordNotFound ) {
return nil , handlers . NotFoundErrorf ( "AuthMethod %q doesn't exist." , id )
}
return nil , err
}
if u == nil {
return nil , handlers . NotFoundErrorf ( "AuthMethod %q doesn't exist." , id )
}
return toProto ( u )
}
func ( s Service ) listFromRepo ( ctx context . Context , scopeId string ) ([] * pb . AuthMethod , error ) {
repo , err := s . repoFn ()
if err != nil {
return nil , err
}
ul , err := repo . ListAuthMethods ( ctx , scopeId )
if err != nil {
return nil , err
}
var outUl [] * pb . AuthMethod
for _ , u := range ul {
ou , err := toProto ( u )
if err != nil {
return nil , err
}
outUl = append ( outUl , ou )
}
return outUl , nil
}
func ( s Service ) createInRepo ( ctx context . Context , scopeId string , item * pb . AuthMethod ) ( * pb . AuthMethod , error ) {
var opts [] password . Option
if item . GetName () != nil {
opts = append ( opts , password . WithName ( item . GetName (). GetValue ()))
}
if item . GetDescription () != nil {
opts = append ( opts , password . WithDescription ( item . GetDescription (). GetValue ()))
}
u , err := password . NewAuthMethod ( scopeId , opts ... )
if err != nil {
return nil , status . Errorf ( codes . Internal , "Unable to build auth method for creation: %v." , err )
}
repo , err := s . repoFn ()
if err != nil {
return nil , err
}
out , err := repo . CreateAuthMethod ( ctx , u )
if err != nil {
return nil , status . Errorf ( codes . Internal , "Unable to create auth method: %v." , err )
}
if out == nil {
return nil , status . Error ( codes . Internal , "Unable to create auth method but no error returned from repository." )
}
return toProto ( out )
}
2020-08-18 17:38:38 -07:00
func ( s Service ) updateInRepo ( ctx context . Context , scopeId , id string , mask [] string , item * pb . AuthMethod ) ( * pb . AuthMethod , error ) {
2020-08-06 12:50:20 -07:00
var opts [] password . Option
if desc := item . GetDescription (); desc != nil {
opts = append ( opts , password . WithDescription ( desc . GetValue ()))
}
if name := item . GetName (); name != nil {
opts = append ( opts , password . WithName ( name . GetValue ()))
}
u , err := password . NewAuthMethod ( scopeId , opts ... )
if err != nil {
return nil , status . Errorf ( codes . Internal , "Unable to build auth method for update: %v." , err )
}
2020-08-14 14:08:12 -07:00
pwAttrs := & pb . PasswordAuthMethodAttributes {}
if err := handlers . StructToProto ( item . GetAttributes (), pwAttrs ); err != nil {
return nil , status . Errorf ( codes . InvalidArgument , "Provided attributes don't match expected format." )
}
if pwAttrs . GetMinLoginNameLength () != 0 {
u . MinLoginNameLength = pwAttrs . GetMinLoginNameLength ()
}
if pwAttrs . GetMinPasswordLength () != 0 {
u . MinPasswordLength = pwAttrs . GetMinPasswordLength ()
}
2020-08-18 17:38:38 -07:00
version := item . GetVersion ()
2020-08-14 14:08:12 -07:00
2020-08-06 12:50:20 -07:00
u . PublicId = id
dbMask := maskManager . Translate ( mask )
if len ( dbMask ) == 0 {
2020-08-28 00:17:09 -04:00
return nil , handlers . InvalidArgumentErrorf ( "No valid fields included in the update mask." , map [ string ] string { "update_mask" : "No valid fields provided in the update mask." })
2020-08-06 12:50:20 -07:00
}
repo , err := s . repoFn ()
if err != nil {
return nil , err
}
2020-08-11 11:19:29 -04:00
out , rowsUpdated , err := repo . UpdateAuthMethod ( ctx , u , version , dbMask )
2020-08-06 12:50:20 -07:00
if err != nil {
return nil , status . Errorf ( codes . Internal , "Unable to update auth method: %v." , err )
}
if rowsUpdated == 0 {
return nil , handlers . NotFoundErrorf ( "AuthMethod %q doesn't exist." , id )
}
return toProto ( out )
}
2020-08-19 16:47:18 -04:00
func ( s Service ) deleteFromRepo ( ctx context . Context , scopeId , id string ) ( bool , error ) {
2020-08-06 12:50:20 -07:00
repo , err := s . repoFn ()
if err != nil {
return false , err
}
2020-08-19 16:47:18 -04:00
rows , err := repo . DeleteAuthMethod ( ctx , scopeId , id )
2020-08-06 12:50:20 -07:00
if err != nil {
if errors . Is ( err , db . ErrRecordNotFound ) {
return false , nil
}
return false , status . Errorf ( codes . Internal , "Unable to delete auth method: %v." , err )
}
return rows > 0 , nil
}
2020-09-04 14:48:11 -07:00
func ( s Service ) authResult ( ctx context . Context , id string , a action . Type ) auth . VerifyResults {
res := auth . VerifyResults {}
var parentId string
opts := [] auth . Option { auth . WithType ( resource . AuthMethod ), auth . WithAction ( a )}
switch a {
case action . List , action . Create :
parentId = id
iamRepo , err := s . iamRepoFn ()
if err != nil {
res . Error = err
return res
}
scp , err := iamRepo . LookupScope ( ctx , parentId )
if err != nil {
res . Error = err
return res
}
if scp == nil {
2020-09-08 23:57:26 -04:00
res . Error = handlers . NotFoundError ()
2020-09-04 14:48:11 -07:00
return res
}
default :
repo , err := s . repoFn ()
if err != nil {
res . Error = err
return res
}
authMeth , err := repo . LookupAuthMethod ( ctx , id )
if err != nil {
res . Error = err
return res
}
if authMeth == nil {
2020-09-08 23:57:26 -04:00
res . Error = handlers . NotFoundError ()
2020-09-04 14:48:11 -07:00
return res
}
parentId = authMeth . GetScopeId ()
opts = append ( opts , auth . WithId ( id ))
}
opts = append ( opts , auth . WithScopeId ( parentId ))
return auth . Verify ( ctx , opts ... )
}
2020-08-06 12:50:20 -07:00
func toProto ( in * password . AuthMethod ) ( * pb . AuthMethod , error ) {
out := pb . AuthMethod {
Id : in . GetPublicId (),
2020-09-02 19:43:41 -07:00
ScopeId : in . GetScopeId (),
2020-08-06 12:50:20 -07:00
CreatedTime : in . GetCreateTime (). GetTimestamp (),
UpdatedTime : in . GetUpdateTime (). GetTimestamp (),
2020-08-11 11:19:29 -04:00
Version : in . GetVersion (),
2020-08-19 10:51:15 -07:00
Type : auth . PasswordSubtype . String (),
2020-08-06 12:50:20 -07:00
}
if in . GetDescription () != "" {
2020-08-14 14:08:12 -07:00
out . Description = wrapperspb . String ( in . GetDescription ())
2020-08-06 12:50:20 -07:00
}
if in . GetName () != "" {
2020-08-14 14:08:12 -07:00
out . Name = wrapperspb . String ( in . GetName ())
2020-08-06 12:50:20 -07:00
}
st , err := handlers . ProtoToStruct ( & pb . PasswordAuthMethodAttributes {
2020-08-11 17:25:00 -04:00
MinLoginNameLength : in . GetMinLoginNameLength (),
MinPasswordLength : in . GetMinPasswordLength (),
2020-08-06 12:50:20 -07:00
})
if err != nil {
return nil , status . Errorf ( codes . Internal , "failed building password attribute struct: %v" , err )
}
out . Attributes = st
return & out , nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
// requirements on the structure of the request. They verify that:
// * The path passed in is correctly formatted
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest ( req * pbs . GetAuthMethodRequest ) error {
2020-08-21 08:15:32 -07:00
return handlers . ValidateGetRequest ( password . AuthMethodPrefix , req , handlers . NoopValidatorFn )
2020-08-06 12:50:20 -07:00
}
func validateCreateRequest ( req * pbs . CreateAuthMethodRequest ) error {
2020-08-21 08:15:32 -07:00
return handlers . ValidateCreateRequest ( req . GetItem (), func () map [ string ] string {
badFields := map [ string ] string {}
2020-09-03 08:02:30 -07:00
if ! handlers . ValidId ( scope . Org . Prefix (), req . GetItem (). GetScopeId ()) &&
scope . Global . String () != req . GetItem (). GetScopeId () {
badFields [ "scope_id" ] = "This field is missing or improperly formatted."
}
2020-08-21 08:15:32 -07:00
switch auth . SubtypeFromType ( req . GetItem (). GetType ()) {
case auth . PasswordSubtype :
pwAttrs := & pb . PasswordAuthMethodAttributes {}
if err := handlers . StructToProto ( req . GetItem (). GetAttributes (), pwAttrs ); err != nil {
badFields [ "attributes" ] = "Attribute fields do not match the expected format."
}
default :
badFields [ "type" ] = fmt . Sprintf ( "This is a required field and must be %q." , auth . PasswordSubtype . String ())
2020-08-06 12:50:20 -07:00
}
2020-08-21 08:15:32 -07:00
return badFields
})
2020-08-06 12:50:20 -07:00
}
func validateUpdateRequest ( req * pbs . UpdateAuthMethodRequest ) error {
2020-08-21 08:15:32 -07:00
return handlers . ValidateUpdateRequest ( password . AuthMethodPrefix , req , req . GetItem (), func () map [ string ] string {
badFields := map [ string ] string {}
2020-09-08 16:04:19 -07:00
switch auth . SubtypeFromId ( req . GetId ()) {
case auth . PasswordSubtype :
if req . GetItem (). GetType () != "" && auth . SubtypeFromType ( req . GetItem (). GetType ()) != auth . PasswordSubtype {
badFields [ "type" ] = "Cannot modify resource type."
}
pwAttrs := & pb . PasswordAuthMethodAttributes {}
if err := handlers . StructToProto ( req . GetItem (). GetAttributes (), pwAttrs ); err != nil {
badFields [ "attributes" ] = "Attribute fields do not match the expected format."
}
default :
badFields [ "id" ] = "Incorrectly formatted identifier."
2020-08-21 08:15:32 -07:00
}
return badFields
})
2020-08-06 12:50:20 -07:00
}
func validateDeleteRequest ( req * pbs . DeleteAuthMethodRequest ) error {
2020-08-21 08:15:32 -07:00
return handlers . ValidateDeleteRequest ( password . AuthMethodPrefix , req , handlers . NoopValidatorFn )
}
2020-09-08 23:57:26 -04:00
func validateListRequest ( req * pbs . ListAuthMethodsRequest ) error {
2020-08-06 12:50:20 -07:00
badFields := map [ string ] string {}
2020-09-08 23:57:26 -04:00
if ! handlers . ValidId ( scope . Org . Prefix (), req . GetScopeId ()) &&
req . GetScopeId () != scope . Global . String () {
badFields [ "scope_id" ] = "This field must be a valid project scope id."
}
2020-08-06 12:50:20 -07:00
if len ( badFields ) > 0 {
2020-08-21 08:15:32 -07:00
return handlers . InvalidArgumentErrorf ( "Improperly formatted identifier." , badFields )
2020-08-06 12:50:20 -07:00
}
return nil
}