2020-08-06 12:50:20 -07:00
package authmethods
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
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"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
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
reInvalidID = regexp . MustCompile ( "[^A-Za-z0-9]" )
)
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 {
repoFn func () ( * password . Repository , error )
}
2020-08-10 17:15:02 -04:00
// NewService returns a auth method service which handles auth method related requests to boundary.
2020-08-06 12:50:20 -07:00
func NewService ( repo func () ( * password . Repository , error )) ( Service , error ) {
if repo == nil {
return Service {}, fmt . Errorf ( "nil iam repository provided" )
}
return Service { repoFn : repo }, nil
}
var _ pbs . AuthMethodServiceServer = Service {}
// ListAuthMethods implements the interface pbs.AuthMethodServiceServer.
func ( s Service ) ListAuthMethods ( ctx context . Context , req * pbs . ListAuthMethodsRequest ) ( * pbs . ListAuthMethodsResponse , error ) {
authResults := auth . Verify ( ctx )
2020-08-16 00:42:36 -04:00
if authResults . Error != nil {
return nil , authResults . Error
2020-08-06 12:50:20 -07:00
}
if err := validateListRequest ( req ); err != nil {
return nil , err
}
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
}
// GetAuthMethods implements the interface pbs.AuthMethodServiceServer.
func ( s Service ) GetAuthMethod ( ctx context . Context , req * pbs . GetAuthMethodRequest ) ( * pbs . GetAuthMethodResponse , error ) {
authResults := auth . Verify ( ctx )
2020-08-16 00:42:36 -04:00
if authResults . Error != nil {
return nil , authResults . Error
2020-08-06 12:50:20 -07:00
}
if err := validateGetRequest ( req ); err != nil {
return nil , err
}
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 ) {
authResults := auth . Verify ( ctx )
2020-08-16 00:42:36 -04:00
if authResults . Error != nil {
return nil , authResults . Error
2020-08-06 12:50:20 -07:00
}
if err := validateCreateRequest ( req ); err != nil {
return nil , err
}
u , err := s . createInRepo ( ctx , authResults . Scope . GetId (), req . GetItem ())
if err != nil {
return nil , err
}
u . Scope = authResults . Scope
return & pbs . CreateAuthMethodResponse { Item : u , Uri : fmt . Sprintf ( "scopes/%s/auth-methods/%s" , authResults . Scope . GetId (), u . GetId ())}, nil
}
// UpdateAuthMethod implements the interface pbs.AuthMethodServiceServer.
func ( s Service ) UpdateAuthMethod ( ctx context . Context , req * pbs . UpdateAuthMethodRequest ) ( * pbs . UpdateAuthMethodResponse , error ) {
authResults := auth . Verify ( ctx )
2020-08-16 00:42:36 -04:00
if authResults . Error != nil {
return nil , authResults . Error
2020-08-06 12:50:20 -07:00
}
if err := validateUpdateRequest ( req ); err != nil {
return nil , err
}
2020-08-11 11:19:29 -04:00
u , err := s . updateInRepo ( ctx , authResults . Scope . GetId (), req . GetId (), req . GetVersion (), 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 ) {
authResults := auth . Verify ( ctx )
2020-08-16 00:42:36 -04:00
if authResults . Error != nil {
return nil , authResults . Error
2020-08-06 12:50:20 -07:00
}
if err := validateDeleteRequest ( req ); err != nil {
return nil , err
}
existed , err := s . deleteFromRepo ( ctx , req . GetId ())
if err != nil {
return nil , err
}
return & pbs . DeleteAuthMethodResponse { Existed : existed }, nil
}
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-11 11:19:29 -04:00
func ( s Service ) updateInRepo ( ctx context . Context , scopeId , id string , version uint32 , 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-06 12:50:20 -07:00
u . PublicId = id
dbMask := maskManager . Translate ( mask )
if len ( dbMask ) == 0 {
return nil , handlers . InvalidArgumentErrorf ( "No valid fields included in the update mask." , map [ string ] string { "update_mask" : "No valid paths provided in the update mask." })
}
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 )
}
func ( s Service ) deleteFromRepo ( ctx context . Context , id string ) ( bool , error ) {
repo , err := s . repoFn ()
if err != nil {
return false , err
}
rows , err := repo . DeleteAuthMethod ( ctx , id )
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
}
func toProto ( in * password . AuthMethod ) ( * pb . AuthMethod , error ) {
out := pb . AuthMethod {
Id : in . GetPublicId (),
CreatedTime : in . GetCreateTime (). GetTimestamp (),
UpdatedTime : in . GetUpdateTime (). GetTimestamp (),
2020-08-11 11:19:29 -04:00
Version : in . GetVersion (),
2020-08-06 12:50:20 -07:00
Type : "password" ,
}
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 {
badFields := map [ string ] string {}
if ! validId ( req . GetId (), password . AuthMethodPrefix + "_" ) {
badFields [ "id" ] = "Invalid formatted id."
}
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Improperly formatted identifier." , badFields )
}
return nil
}
func validateListRequest ( req * pbs . ListAuthMethodsRequest ) error {
badFields := map [ string ] string {}
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Improperly formatted identifier." , badFields )
}
return nil
}
func validateCreateRequest ( req * pbs . CreateAuthMethodRequest ) error {
badFields := map [ string ] string {}
item := req . GetItem ()
if item . GetId () != "" {
badFields [ "id" ] = "This is a read only field."
}
if item . GetCreatedTime () != nil {
badFields [ "created_time" ] = "This is a read only field."
}
if item . GetUpdatedTime () != nil {
badFields [ "updated_time" ] = "This is a read only field."
}
switch item . GetType () {
case "password" :
pwAttrs := & pb . PasswordAuthMethodAttributes {}
if err := handlers . StructToProto ( item . GetAttributes (), pwAttrs ); err != nil {
badFields [ "attributes" ] = "Attribute fields do not match the expected format."
}
default :
badFields [ "type" ] = "This is a required field and must be \"password\"."
}
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Argument errors found in the request." , badFields )
}
return nil
}
func validateUpdateRequest ( req * pbs . UpdateAuthMethodRequest ) error {
badFields := map [ string ] string {}
if ! validId ( req . GetId (), password . AuthMethodPrefix + "_" ) {
badFields [ "id" ] = "Improperly formatted identifier."
}
if req . GetUpdateMask () == nil {
badFields [ "update_mask" ] = "UpdateMask not provided but is required to update this resource."
}
2020-08-11 11:19:29 -04:00
if req . GetVersion () == 0 {
badFields [ "version" ] = "Existing resource version is required for an update."
}
2020-08-06 12:50:20 -07:00
item := req . GetItem ()
if item == nil {
// It is legitimate for no item to be specified in an update request as it indicates all fields provided in
// the mask will be marked as unset.
return nil
}
if item . GetId () != "" {
badFields [ "id" ] = "This is a read only field and cannot be specified in an update request."
}
if item . GetCreatedTime () != nil {
badFields [ "created_time" ] = "This is a read only field and cannot be specified in an update request."
}
if item . GetUpdatedTime () != nil {
badFields [ "updated_time" ] = "This is a read only field and cannot be specified in an update request."
}
if item . GetType () != "" {
badFields [ "type" ] = "This is a read only field and cannot be specified in an update request."
}
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Errors in provided fields." , badFields )
}
return nil
}
func validateDeleteRequest ( req * pbs . DeleteAuthMethodRequest ) error {
badFields := map [ string ] string {}
if ! validId ( req . GetId (), password . AuthMethodPrefix + "_" ) {
badFields [ "id" ] = "Incorrectly formatted identifier."
}
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Errors in provided fields." , badFields )
}
return nil
}
func validId ( id , prefix string ) bool {
if ! strings . HasPrefix ( id , prefix ) {
return false
}
id = strings . TrimPrefix ( id , prefix )
return ! reInvalidID . Match ([] byte ( id ))
}