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