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 }