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 }