github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/api/users.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	neturl "net/url"
    10  	"strings"
    11  
    12  	"code.cloudfoundry.org/cli/cf/api/resources"
    13  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    14  	"code.cloudfoundry.org/cli/cf/errors"
    15  	. "code.cloudfoundry.org/cli/cf/i18n"
    16  	"code.cloudfoundry.org/cli/cf/models"
    17  	"code.cloudfoundry.org/cli/cf/net"
    18  )
    19  
    20  var orgRoleToPathMap = map[models.Role]string{
    21  	models.RoleOrgUser:        "users",
    22  	models.RoleOrgManager:     "managers",
    23  	models.RoleBillingManager: "billing_managers",
    24  	models.RoleOrgAuditor:     "auditors",
    25  }
    26  
    27  var spaceRoleToPathMap = map[models.Role]string{
    28  	models.RoleSpaceManager:   "managers",
    29  	models.RoleSpaceDeveloper: "developers",
    30  	models.RoleSpaceAuditor:   "auditors",
    31  }
    32  
    33  type apiErrResponse struct {
    34  	Code        int    `json:"code,omitempty"`
    35  	ErrorCode   string `json:"error_code,omitempty"`
    36  	Description string `json:"description,omitempty"`
    37  }
    38  
    39  //go:generate counterfeiter . UserRepository
    40  
    41  type UserRepository interface {
    42  	FindByUsername(username string) (user models.UserFields, apiErr error)
    43  	FindAllByUsername(username string) (users []models.UserFields, apiErr error)
    44  	ListUsersInOrgForRoleWithNoUAA(orgGUID string, role models.Role) ([]models.UserFields, error)
    45  	ListUsersInSpaceForRoleWithNoUAA(spaceGUID string, role models.Role) ([]models.UserFields, error)
    46  	Create(username, password string) (apiErr error)
    47  	Delete(userGUID string) (apiErr error)
    48  	SetOrgRoleByGUID(userGUID, orgGUID string, role models.Role) (apiErr error)
    49  	SetOrgRoleByUsername(username, orgGUID string, role models.Role) (apiErr error)
    50  	UnsetOrgRoleByGUID(userGUID, orgGUID string, role models.Role) (apiErr error)
    51  	UnsetOrgRoleByUsername(username, orgGUID string, role models.Role) (apiErr error)
    52  	SetSpaceRoleByGUID(userGUID, spaceGUID, orgGUID string, role models.Role) (apiErr error)
    53  	SetSpaceRoleByUsername(username, spaceGUID, orgGUID string, role models.Role) (apiErr error)
    54  	UnsetSpaceRoleByGUID(userGUID, spaceGUID string, role models.Role) (apiErr error)
    55  	UnsetSpaceRoleByUsername(userGUID, spaceGUID string, role models.Role) (apiErr error)
    56  }
    57  
    58  type CloudControllerUserRepository struct {
    59  	config     coreconfig.Reader
    60  	uaaGateway net.Gateway
    61  	ccGateway  net.Gateway
    62  }
    63  
    64  func NewCloudControllerUserRepository(config coreconfig.Reader, uaaGateway net.Gateway, ccGateway net.Gateway) (repo CloudControllerUserRepository) {
    65  	repo.config = config
    66  	repo.uaaGateway = uaaGateway
    67  	repo.ccGateway = ccGateway
    68  	return
    69  }
    70  
    71  func (repo CloudControllerUserRepository) FindByUsername(username string) (user models.UserFields, apiErr error) {
    72  	users, apiErr := repo.FindAllByUsername(username)
    73  	if apiErr != nil {
    74  		return user, apiErr
    75  	}
    76  	if len(users) > 1 {
    77  		return user, errors.New("The user exists in multiple origins.")
    78  	}
    79  
    80  	user = users[0]
    81  
    82  	return user, nil
    83  }
    84  
    85  func (repo CloudControllerUserRepository) FindAllByUsername(username string) (users []models.UserFields, apiErr error) {
    86  	uaaEndpoint, apiErr := repo.getAuthEndpoint()
    87  	if apiErr != nil {
    88  		return users, apiErr
    89  	}
    90  
    91  	usernameFilter := neturl.QueryEscape(fmt.Sprintf(`userName Eq "%s"`, username))
    92  	path := fmt.Sprintf("%s/Users?attributes=id,userName&filter=%s", uaaEndpoint, usernameFilter)
    93  	users, apiErr = repo.updateOrFindUsersWithUAAPath([]models.UserFields{}, path)
    94  
    95  	if apiErr != nil {
    96  		errType, ok := apiErr.(errors.HTTPError)
    97  		if ok {
    98  			if errType.StatusCode() == 403 {
    99  				return users, errors.NewAccessDeniedError()
   100  			}
   101  		}
   102  		return users, apiErr
   103  	} else if len(users) == 0 {
   104  		return users, errors.NewModelNotFoundError("User", username)
   105  	}
   106  
   107  	return users, apiErr
   108  }
   109  
   110  func (repo CloudControllerUserRepository) ListUsersInOrgForRoleWithNoUAA(orgGUID string, roleName models.Role) (users []models.UserFields, apiErr error) {
   111  	return repo.listUsersWithPathWithNoUAA(fmt.Sprintf("/v2/organizations/%s/%s", orgGUID, orgRoleToPathMap[roleName]))
   112  }
   113  
   114  func (repo CloudControllerUserRepository) ListUsersInSpaceForRoleWithNoUAA(spaceGUID string, roleName models.Role) (users []models.UserFields, apiErr error) {
   115  	return repo.listUsersWithPathWithNoUAA(fmt.Sprintf("/v2/spaces/%s/%s", spaceGUID, spaceRoleToPathMap[roleName]))
   116  }
   117  
   118  func (repo CloudControllerUserRepository) listUsersWithPathWithNoUAA(path string) (users []models.UserFields, apiErr error) {
   119  	apiErr = repo.ccGateway.ListPaginatedResources(
   120  		repo.config.APIEndpoint(),
   121  		path,
   122  		resources.UserResource{},
   123  		func(resource interface{}) bool {
   124  			user := resource.(resources.UserResource).ToFields()
   125  			users = append(users, user)
   126  			return true
   127  		})
   128  	if apiErr != nil {
   129  		return
   130  	}
   131  
   132  	return
   133  }
   134  
   135  func (repo CloudControllerUserRepository) updateOrFindUsersWithUAAPath(ccUsers []models.UserFields, path string) (updatedUsers []models.UserFields, apiErr error) {
   136  	uaaResponse := new(resources.UAAUserResources)
   137  	apiErr = repo.uaaGateway.GetResource(path, uaaResponse)
   138  	if apiErr != nil {
   139  		return
   140  	}
   141  
   142  	for _, uaaResource := range uaaResponse.Resources {
   143  		var ccUserFields models.UserFields
   144  
   145  		for _, u := range ccUsers {
   146  			if u.GUID == uaaResource.ID {
   147  				ccUserFields = u
   148  				break
   149  			}
   150  		}
   151  
   152  		updatedUsers = append(updatedUsers, models.UserFields{
   153  			GUID:     uaaResource.ID,
   154  			Username: uaaResource.Username,
   155  			IsAdmin:  ccUserFields.IsAdmin,
   156  		})
   157  	}
   158  	return
   159  }
   160  
   161  func (repo CloudControllerUserRepository) Create(username, password string) (err error) {
   162  	uaaEndpoint, err := repo.getAuthEndpoint()
   163  	if err != nil {
   164  		return
   165  	}
   166  
   167  	path := "/Users"
   168  	body, err := json.Marshal(resources.NewUAAUserResource(username, password))
   169  
   170  	if err != nil {
   171  		return
   172  	}
   173  
   174  	createUserResponse := &resources.UAAUserFields{}
   175  	err = repo.uaaGateway.CreateResource(uaaEndpoint, path, bytes.NewReader(body), createUserResponse)
   176  	switch httpErr := err.(type) {
   177  	case nil:
   178  	case errors.HTTPError:
   179  		if httpErr.StatusCode() == http.StatusConflict {
   180  			err = errors.NewModelAlreadyExistsError("user", username)
   181  			return
   182  		}
   183  		return
   184  	default:
   185  		return
   186  	}
   187  
   188  	path = "/v2/users"
   189  	body, err = json.Marshal(resources.Metadata{
   190  		GUID: createUserResponse.ID,
   191  	})
   192  
   193  	if err != nil {
   194  		return
   195  	}
   196  
   197  	return repo.ccGateway.CreateResource(repo.config.APIEndpoint(), path, bytes.NewReader(body))
   198  }
   199  
   200  func (repo CloudControllerUserRepository) Delete(userGUID string) (apiErr error) {
   201  	path := fmt.Sprintf("/v2/users/%s", userGUID)
   202  
   203  	apiErr = repo.ccGateway.DeleteResource(repo.config.APIEndpoint(), path)
   204  
   205  	if httpErr, ok := apiErr.(errors.HTTPError); ok && httpErr.ErrorCode() != errors.UserNotFound {
   206  		return
   207  	}
   208  	uaaEndpoint, apiErr := repo.getAuthEndpoint()
   209  	if apiErr != nil {
   210  		return
   211  	}
   212  
   213  	path = fmt.Sprintf("/Users/%s", userGUID)
   214  	return repo.uaaGateway.DeleteResource(uaaEndpoint, path)
   215  }
   216  
   217  func (repo CloudControllerUserRepository) SetOrgRoleByGUID(userGUID string, orgGUID string, role models.Role) (err error) {
   218  	path, err := userGUIDPath(repo.config.APIEndpoint(), userGUID, orgGUID, role)
   219  	if err != nil {
   220  		return
   221  	}
   222  	err = repo.callAPI("PUT", path, nil)
   223  	if err != nil {
   224  		return
   225  	}
   226  	return repo.assocUserWithOrgByUserGUID(userGUID, orgGUID)
   227  }
   228  
   229  func (repo CloudControllerUserRepository) UnsetOrgRoleByGUID(userGUID, orgGUID string, role models.Role) (err error) {
   230  	path, err := userGUIDPath(repo.config.APIEndpoint(), userGUID, orgGUID, role)
   231  	if err != nil {
   232  		return
   233  	}
   234  	return repo.callAPI("DELETE", path, nil)
   235  }
   236  
   237  func (repo CloudControllerUserRepository) UnsetOrgRoleByUsername(username, orgGUID string, role models.Role) error {
   238  	rolePath, err := rolePath(role)
   239  	if err != nil {
   240  		return err
   241  	}
   242  
   243  	path := fmt.Sprintf("%s/v2/organizations/%s/%s", repo.config.APIEndpoint(), orgGUID, rolePath)
   244  
   245  	return repo.callAPI("DELETE", path, usernamePayload(username))
   246  }
   247  
   248  func (repo CloudControllerUserRepository) UnsetSpaceRoleByUsername(username, spaceGUID string, role models.Role) error {
   249  	rolePath := spaceRoleToPathMap[role]
   250  	path := fmt.Sprintf("%s/v2/spaces/%s/%s", repo.config.APIEndpoint(), spaceGUID, rolePath)
   251  
   252  	return repo.callAPI("DELETE", path, usernamePayload(username))
   253  }
   254  
   255  func (repo CloudControllerUserRepository) SetOrgRoleByUsername(username string, orgGUID string, role models.Role) error {
   256  	rolePath, err := rolePath(role)
   257  	if err != nil {
   258  		return err
   259  	}
   260  
   261  	path := fmt.Sprintf("%s/v2/organizations/%s/%s", repo.config.APIEndpoint(), orgGUID, rolePath)
   262  	err = repo.callAPI("PUT", path, usernamePayload(username))
   263  	if err != nil {
   264  		return err
   265  	}
   266  	return repo.assocUserWithOrgByUsername(username, orgGUID, nil)
   267  }
   268  
   269  func (repo CloudControllerUserRepository) callAPI(verb, path string, body io.ReadSeeker) (err error) {
   270  	request, err := repo.ccGateway.NewRequest(verb, path, repo.config.AccessToken(), body)
   271  	if err != nil {
   272  		return
   273  	}
   274  	_, err = repo.ccGateway.PerformRequest(request)
   275  	if err != nil {
   276  		return
   277  	}
   278  	return
   279  }
   280  
   281  func userGUIDPath(apiEndpoint, userGUID, orgGUID string, role models.Role) (string, error) {
   282  	rolePath, err := rolePath(role)
   283  	if err != nil {
   284  		return "", err
   285  	}
   286  
   287  	return fmt.Sprintf("%s/v2/organizations/%s/%s/%s", apiEndpoint, orgGUID, rolePath, userGUID), nil
   288  }
   289  
   290  func (repo CloudControllerUserRepository) SetSpaceRoleByGUID(userGUID, spaceGUID, orgGUID string, role models.Role) error {
   291  	rolePath, found := spaceRoleToPathMap[role]
   292  	if !found {
   293  		return fmt.Errorf(T("Invalid Role {{.Role}}", map[string]interface{}{"Role": role}))
   294  	}
   295  
   296  	err := repo.assocUserWithOrgByUserGUID(userGUID, orgGUID)
   297  	if err != nil {
   298  		return err
   299  	}
   300  
   301  	path := fmt.Sprintf("/v2/spaces/%s/%s/%s", spaceGUID, rolePath, userGUID)
   302  
   303  	return repo.ccGateway.UpdateResource(repo.config.APIEndpoint(), path, nil)
   304  }
   305  
   306  func (repo CloudControllerUserRepository) SetSpaceRoleByUsername(username, spaceGUID, orgGUID string, role models.Role) (apiErr error) {
   307  	rolePath, apiErr := repo.checkSpaceRole(spaceGUID, role)
   308  	if apiErr != nil {
   309  		return
   310  	}
   311  
   312  	setOrgRoleErr := apiErrResponse{}
   313  	apiErr = repo.assocUserWithOrgByUsername(username, orgGUID, &setOrgRoleErr)
   314  	if setOrgRoleErr.Code == 10003 {
   315  		//operator lacking the privilege to set org role
   316  		//user might already be in org, so ignoring error and attempt to set space role
   317  	} else if apiErr != nil {
   318  		return
   319  	}
   320  
   321  	setSpaceRoleErr := apiErrResponse{}
   322  	apiErr = repo.ccGateway.UpdateResourceSync(repo.config.APIEndpoint(), rolePath, usernamePayload(username), &setSpaceRoleErr)
   323  	if setSpaceRoleErr.Code == 1002 {
   324  		return errors.New(T("Server error, error code: 1002, message: cannot set space role because user is not part of the org"))
   325  	}
   326  
   327  	return apiErr
   328  }
   329  
   330  func (repo CloudControllerUserRepository) UnsetSpaceRoleByGUID(userGUID, spaceGUID string, role models.Role) error {
   331  	rolePath, found := spaceRoleToPathMap[role]
   332  	if !found {
   333  		return fmt.Errorf(T("Invalid Role {{.Role}}", map[string]interface{}{"Role": role}))
   334  	}
   335  	apiURL := fmt.Sprintf("/v2/spaces/%s/%s/%s", spaceGUID, rolePath, userGUID)
   336  
   337  	return repo.ccGateway.DeleteResource(repo.config.APIEndpoint(), apiURL)
   338  }
   339  
   340  func (repo CloudControllerUserRepository) checkSpaceRole(spaceGUID string, role models.Role) (string, error) {
   341  	var apiErr error
   342  
   343  	rolePath, found := spaceRoleToPathMap[role]
   344  
   345  	if !found {
   346  		apiErr = fmt.Errorf(T("Invalid Role {{.Role}}",
   347  			map[string]interface{}{"Role": role}))
   348  	}
   349  
   350  	apiPath := fmt.Sprintf("/v2/spaces/%s/%s", spaceGUID, rolePath)
   351  	return apiPath, apiErr
   352  }
   353  
   354  func (repo CloudControllerUserRepository) assocUserWithOrgByUsername(username, orgGUID string, resource interface{}) (apiErr error) {
   355  	path := fmt.Sprintf("/v2/organizations/%s/users", orgGUID)
   356  	return repo.ccGateway.UpdateResourceSync(repo.config.APIEndpoint(), path, usernamePayload(username), resource)
   357  }
   358  
   359  func (repo CloudControllerUserRepository) assocUserWithOrgByUserGUID(userGUID, orgGUID string) (apiErr error) {
   360  	path := fmt.Sprintf("/v2/organizations/%s/users/%s", orgGUID, userGUID)
   361  	return repo.ccGateway.UpdateResource(repo.config.APIEndpoint(), path, nil)
   362  }
   363  
   364  func (repo CloudControllerUserRepository) getAuthEndpoint() (string, error) {
   365  	uaaEndpoint := repo.config.UaaEndpoint()
   366  	if uaaEndpoint == "" {
   367  		return "", errors.New(T("UAA endpoint missing from config file"))
   368  	}
   369  	return uaaEndpoint, nil
   370  }
   371  
   372  func rolePath(role models.Role) (string, error) {
   373  	path, found := orgRoleToPathMap[role]
   374  
   375  	if !found {
   376  		return "", fmt.Errorf(T("Invalid Role {{.Role}}",
   377  			map[string]interface{}{"Role": role}))
   378  	}
   379  	return path, nil
   380  }
   381  
   382  func usernamePayload(username string) *strings.Reader {
   383  	return strings.NewReader(`{"username": "` + username + `"}`)
   384  }