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