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