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