1from gitlab import cli 2from gitlab import exceptions as exc 3from gitlab import types 4from gitlab.base import RequiredOptional, RESTManager, RESTObject 5from gitlab.mixins import ( 6 CreateMixin, 7 CRUDMixin, 8 DeleteMixin, 9 GetWithoutIdMixin, 10 ListMixin, 11 NoUpdateMixin, 12 ObjectDeleteMixin, 13 RetrieveMixin, 14 SaveMixin, 15 UpdateMixin, 16) 17 18from .custom_attributes import UserCustomAttributeManager # noqa: F401 19from .events import UserEventManager # noqa: F401 20 21__all__ = [ 22 "CurrentUserEmail", 23 "CurrentUserEmailManager", 24 "CurrentUserGPGKey", 25 "CurrentUserGPGKeyManager", 26 "CurrentUserKey", 27 "CurrentUserKeyManager", 28 "CurrentUserStatus", 29 "CurrentUserStatusManager", 30 "CurrentUser", 31 "CurrentUserManager", 32 "User", 33 "UserManager", 34 "ProjectUser", 35 "ProjectUserManager", 36 "UserEmail", 37 "UserEmailManager", 38 "UserActivities", 39 "UserStatus", 40 "UserStatusManager", 41 "UserActivitiesManager", 42 "UserGPGKey", 43 "UserGPGKeyManager", 44 "UserKey", 45 "UserKeyManager", 46 "UserIdentityProviderManager", 47 "UserImpersonationToken", 48 "UserImpersonationTokenManager", 49 "UserMembership", 50 "UserMembershipManager", 51 "UserProject", 52 "UserProjectManager", 53] 54 55 56class CurrentUserEmail(ObjectDeleteMixin, RESTObject): 57 _short_print_attr = "email" 58 59 60class CurrentUserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): 61 _path = "/user/emails" 62 _obj_cls = CurrentUserEmail 63 _create_attrs = RequiredOptional(required=("email",)) 64 65 66class CurrentUserGPGKey(ObjectDeleteMixin, RESTObject): 67 pass 68 69 70class CurrentUserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): 71 _path = "/user/gpg_keys" 72 _obj_cls = CurrentUserGPGKey 73 _create_attrs = RequiredOptional(required=("key",)) 74 75 76class CurrentUserKey(ObjectDeleteMixin, RESTObject): 77 _short_print_attr = "title" 78 79 80class CurrentUserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): 81 _path = "/user/keys" 82 _obj_cls = CurrentUserKey 83 _create_attrs = RequiredOptional(required=("title", "key")) 84 85 86class CurrentUserStatus(SaveMixin, RESTObject): 87 _id_attr = None 88 _short_print_attr = "message" 89 90 91class CurrentUserStatusManager(GetWithoutIdMixin, UpdateMixin, RESTManager): 92 _path = "/user/status" 93 _obj_cls = CurrentUserStatus 94 _update_attrs = RequiredOptional(optional=("emoji", "message")) 95 96 97class CurrentUser(RESTObject): 98 _id_attr = None 99 _short_print_attr = "username" 100 _managers = ( 101 ("status", "CurrentUserStatusManager"), 102 ("emails", "CurrentUserEmailManager"), 103 ("gpgkeys", "CurrentUserGPGKeyManager"), 104 ("keys", "CurrentUserKeyManager"), 105 ) 106 107 108class CurrentUserManager(GetWithoutIdMixin, RESTManager): 109 _path = "/user" 110 _obj_cls = CurrentUser 111 112 113class User(SaveMixin, ObjectDeleteMixin, RESTObject): 114 _short_print_attr = "username" 115 _managers = ( 116 ("customattributes", "UserCustomAttributeManager"), 117 ("emails", "UserEmailManager"), 118 ("followers_users", "UserFollowersManager"), 119 ("following_users", "UserFollowingManager"), 120 ("events", "UserEventManager"), 121 ("gpgkeys", "UserGPGKeyManager"), 122 ("identityproviders", "UserIdentityProviderManager"), 123 ("impersonationtokens", "UserImpersonationTokenManager"), 124 ("keys", "UserKeyManager"), 125 ("memberships", "UserMembershipManager"), 126 ("projects", "UserProjectManager"), 127 ("status", "UserStatusManager"), 128 ) 129 130 @cli.register_custom_action("User") 131 @exc.on_http_error(exc.GitlabBlockError) 132 def block(self, **kwargs): 133 """Block the user. 134 135 Args: 136 **kwargs: Extra options to send to the server (e.g. sudo) 137 138 Raises: 139 GitlabAuthenticationError: If authentication is not correct 140 GitlabBlockError: If the user could not be blocked 141 142 Returns: 143 bool: Whether the user status has been changed 144 """ 145 path = "/users/%s/block" % self.id 146 server_data = self.manager.gitlab.http_post(path, **kwargs) 147 if server_data is True: 148 self._attrs["state"] = "blocked" 149 return server_data 150 151 @cli.register_custom_action("User") 152 @exc.on_http_error(exc.GitlabFollowError) 153 def follow(self, **kwargs): 154 """Follow the user. 155 156 Args: 157 **kwargs: Extra options to send to the server (e.g. sudo) 158 159 Raises: 160 GitlabAuthenticationError: If authentication is not correct 161 GitlabFollowError: If the user could not be followed 162 163 Returns: 164 dict: The new object data (*not* a RESTObject) 165 """ 166 path = "/users/%s/follow" % self.id 167 return self.manager.gitlab.http_post(path, **kwargs) 168 169 @cli.register_custom_action("User") 170 @exc.on_http_error(exc.GitlabUnfollowError) 171 def unfollow(self, **kwargs): 172 """Unfollow the user. 173 174 Args: 175 **kwargs: Extra options to send to the server (e.g. sudo) 176 177 Raises: 178 GitlabAuthenticationError: If authentication is not correct 179 GitlabUnfollowError: If the user could not be followed 180 181 Returns: 182 dict: The new object data (*not* a RESTObject) 183 """ 184 path = "/users/%s/unfollow" % self.id 185 return self.manager.gitlab.http_post(path, **kwargs) 186 187 @cli.register_custom_action("User") 188 @exc.on_http_error(exc.GitlabUnblockError) 189 def unblock(self, **kwargs): 190 """Unblock the user. 191 192 Args: 193 **kwargs: Extra options to send to the server (e.g. sudo) 194 195 Raises: 196 GitlabAuthenticationError: If authentication is not correct 197 GitlabUnblockError: If the user could not be unblocked 198 199 Returns: 200 bool: Whether the user status has been changed 201 """ 202 path = "/users/%s/unblock" % self.id 203 server_data = self.manager.gitlab.http_post(path, **kwargs) 204 if server_data is True: 205 self._attrs["state"] = "active" 206 return server_data 207 208 @cli.register_custom_action("User") 209 @exc.on_http_error(exc.GitlabDeactivateError) 210 def deactivate(self, **kwargs): 211 """Deactivate the user. 212 213 Args: 214 **kwargs: Extra options to send to the server (e.g. sudo) 215 216 Raises: 217 GitlabAuthenticationError: If authentication is not correct 218 GitlabDeactivateError: If the user could not be deactivated 219 220 Returns: 221 bool: Whether the user status has been changed 222 """ 223 path = "/users/%s/deactivate" % self.id 224 server_data = self.manager.gitlab.http_post(path, **kwargs) 225 if server_data: 226 self._attrs["state"] = "deactivated" 227 return server_data 228 229 @cli.register_custom_action("User") 230 @exc.on_http_error(exc.GitlabActivateError) 231 def activate(self, **kwargs): 232 """Activate the user. 233 234 Args: 235 **kwargs: Extra options to send to the server (e.g. sudo) 236 237 Raises: 238 GitlabAuthenticationError: If authentication is not correct 239 GitlabActivateError: If the user could not be activated 240 241 Returns: 242 bool: Whether the user status has been changed 243 """ 244 path = "/users/%s/activate" % self.id 245 server_data = self.manager.gitlab.http_post(path, **kwargs) 246 if server_data: 247 self._attrs["state"] = "active" 248 return server_data 249 250 251class UserManager(CRUDMixin, RESTManager): 252 _path = "/users" 253 _obj_cls = User 254 255 _list_filters = ( 256 "active", 257 "blocked", 258 "username", 259 "extern_uid", 260 "provider", 261 "external", 262 "search", 263 "custom_attributes", 264 "status", 265 "two_factor", 266 ) 267 _create_attrs = RequiredOptional( 268 optional=( 269 "email", 270 "username", 271 "name", 272 "password", 273 "reset_password", 274 "skype", 275 "linkedin", 276 "twitter", 277 "projects_limit", 278 "extern_uid", 279 "provider", 280 "bio", 281 "admin", 282 "can_create_group", 283 "website_url", 284 "skip_confirmation", 285 "external", 286 "organization", 287 "location", 288 "avatar", 289 "public_email", 290 "private_profile", 291 "color_scheme_id", 292 "theme_id", 293 ), 294 ) 295 _update_attrs = RequiredOptional( 296 required=("email", "username", "name"), 297 optional=( 298 "password", 299 "skype", 300 "linkedin", 301 "twitter", 302 "projects_limit", 303 "extern_uid", 304 "provider", 305 "bio", 306 "admin", 307 "can_create_group", 308 "website_url", 309 "skip_reconfirmation", 310 "external", 311 "organization", 312 "location", 313 "avatar", 314 "public_email", 315 "private_profile", 316 "color_scheme_id", 317 "theme_id", 318 ), 319 ) 320 _types = {"confirm": types.LowercaseStringAttribute, "avatar": types.ImageAttribute} 321 322 323class ProjectUser(RESTObject): 324 pass 325 326 327class ProjectUserManager(ListMixin, RESTManager): 328 _path = "/projects/%(project_id)s/users" 329 _obj_cls = ProjectUser 330 _from_parent_attrs = {"project_id": "id"} 331 _list_filters = ("search", "skip_users") 332 _types = {"skip_users": types.ListAttribute} 333 334 335class UserEmail(ObjectDeleteMixin, RESTObject): 336 _short_print_attr = "email" 337 338 339class UserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): 340 _path = "/users/%(user_id)s/emails" 341 _obj_cls = UserEmail 342 _from_parent_attrs = {"user_id": "id"} 343 _create_attrs = RequiredOptional(required=("email",)) 344 345 346class UserActivities(RESTObject): 347 _id_attr = "username" 348 349 350class UserStatus(RESTObject): 351 _id_attr = None 352 _short_print_attr = "message" 353 354 355class UserStatusManager(GetWithoutIdMixin, RESTManager): 356 _path = "/users/%(user_id)s/status" 357 _obj_cls = UserStatus 358 _from_parent_attrs = {"user_id": "id"} 359 360 361class UserActivitiesManager(ListMixin, RESTManager): 362 _path = "/user/activities" 363 _obj_cls = UserActivities 364 365 366class UserGPGKey(ObjectDeleteMixin, RESTObject): 367 pass 368 369 370class UserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): 371 _path = "/users/%(user_id)s/gpg_keys" 372 _obj_cls = UserGPGKey 373 _from_parent_attrs = {"user_id": "id"} 374 _create_attrs = RequiredOptional(required=("key",)) 375 376 377class UserKey(ObjectDeleteMixin, RESTObject): 378 pass 379 380 381class UserKeyManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): 382 _path = "/users/%(user_id)s/keys" 383 _obj_cls = UserKey 384 _from_parent_attrs = {"user_id": "id"} 385 _create_attrs = RequiredOptional(required=("title", "key")) 386 387 388class UserIdentityProviderManager(DeleteMixin, RESTManager): 389 """Manager for user identities. 390 391 This manager does not actually manage objects but enables 392 functionality for deletion of user identities by provider. 393 """ 394 395 _path = "/users/%(user_id)s/identities" 396 _from_parent_attrs = {"user_id": "id"} 397 398 399class UserImpersonationToken(ObjectDeleteMixin, RESTObject): 400 pass 401 402 403class UserImpersonationTokenManager(NoUpdateMixin, RESTManager): 404 _path = "/users/%(user_id)s/impersonation_tokens" 405 _obj_cls = UserImpersonationToken 406 _from_parent_attrs = {"user_id": "id"} 407 _create_attrs = RequiredOptional( 408 required=("name", "scopes"), optional=("expires_at",) 409 ) 410 _list_filters = ("state",) 411 412 413class UserMembership(RESTObject): 414 _id_attr = "source_id" 415 416 417class UserMembershipManager(RetrieveMixin, RESTManager): 418 _path = "/users/%(user_id)s/memberships" 419 _obj_cls = UserMembership 420 _from_parent_attrs = {"user_id": "id"} 421 _list_filters = ("type",) 422 423 424# Having this outside projects avoids circular imports due to ProjectUser 425class UserProject(RESTObject): 426 pass 427 428 429class UserProjectManager(ListMixin, CreateMixin, RESTManager): 430 _path = "/projects/user/%(user_id)s" 431 _obj_cls = UserProject 432 _from_parent_attrs = {"user_id": "id"} 433 _create_attrs = RequiredOptional( 434 required=("name",), 435 optional=( 436 "default_branch", 437 "issues_enabled", 438 "wall_enabled", 439 "merge_requests_enabled", 440 "wiki_enabled", 441 "snippets_enabled", 442 "public", 443 "visibility", 444 "description", 445 "builds_enabled", 446 "public_builds", 447 "import_url", 448 "only_allow_merge_if_build_succeeds", 449 ), 450 ) 451 _list_filters = ( 452 "archived", 453 "visibility", 454 "order_by", 455 "sort", 456 "search", 457 "simple", 458 "owned", 459 "membership", 460 "starred", 461 "statistics", 462 "with_issues_enabled", 463 "with_merge_requests_enabled", 464 "with_custom_attributes", 465 "with_programming_language", 466 "wiki_checksum_failed", 467 "repository_checksum_failed", 468 "min_access_level", 469 "id_after", 470 "id_before", 471 ) 472 473 def list(self, **kwargs): 474 """Retrieve a list of objects. 475 476 Args: 477 all (bool): If True, return all the items, without pagination 478 per_page (int): Number of items to retrieve per request 479 page (int): ID of the page to return (starts with page 1) 480 as_list (bool): If set to False and no pagination option is 481 defined, return a generator instead of a list 482 **kwargs: Extra options to send to the server (e.g. sudo) 483 484 Returns: 485 list: The list of objects, or a generator if `as_list` is False 486 487 Raises: 488 GitlabAuthenticationError: If authentication is not correct 489 GitlabListError: If the server cannot perform the request 490 """ 491 if self._parent: 492 path = "/users/%s/projects" % self._parent.id 493 else: 494 path = "/users/%s/projects" % kwargs["user_id"] 495 return ListMixin.list(self, path=path, **kwargs) 496 497 498class UserFollowersManager(ListMixin, RESTManager): 499 _path = "/users/%(user_id)s/followers" 500 _obj_cls = User 501 _from_parent_attrs = {"user_id": "id"} 502 503 504class UserFollowingManager(ListMixin, RESTManager): 505 _path = "/users/%(user_id)s/following" 506 _obj_cls = User 507 _from_parent_attrs = {"user_id": "id"} 508