Skip to content

Commit e890045

Browse files
Merge pull request #66 from adobe-apiplatform/v2
umapi-client 2.11
2 parents c149f36 + 0148ce0 commit e890045

File tree

12 files changed

+678
-153
lines changed

12 files changed

+678
-153
lines changed

docs/usage-instructions-v2.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,175 @@ Each entry in `errors` would then be a dictionary giving
298298
the command that failed, the target user it failed on,
299299
and server information about the reason for the failure.
300300

301+
# Performing Operations on User Groups
302+
303+
User group operations work similarly to user operations. The
304+
UserGroupAction class has a similar interface to the UserAction class.
305+
Currently, the UserGroupAction interface supports the following
306+
operations:
307+
308+
* `create()` - create a new user group
309+
* `update()` - update the name and/or description of an existing user
310+
group
311+
* `delete()` - delete a user group
312+
* `add_users()` - add users to a user group
313+
* `remove_users()` - remove users from a user group
314+
* `add_to_products()` - add one or more product profiles to a user group
315+
* `remove_from_products()` - remove one or more product profiles from
316+
a user group
317+
318+
## Step 1: Specify the User Group
319+
320+
The group name is required to create new UserGroupAction object. For a
321+
new user group, this should be the name of the new group. To perform
322+
an operation on an existing group, specify the name of that group.
323+
324+
Group names:
325+
326+
* Must be unique to the Adobe organization
327+
* May not start with an underscore ("_") - groups with names that begin
328+
with an underscore are reserved for Adobe use
329+
* Must be no longer than 255 characters
330+
331+
Group name is the only required parameter. An optional `requestID` can
332+
be passed to make it easier to connect action requests with API
333+
responses.
334+
335+
```python
336+
from umapi_client import UserGroupAction
337+
group = UserGroupAction(group_name="Employees")
338+
```
339+
340+
## Step 2: Specify the Operations
341+
342+
### Create a New Group
343+
344+
User Groups are created with `UserGroupAction.create()`. Two
345+
optional parameters can be passed:
346+
347+
* `description` - a short description of the group
348+
* `option` - an option specifying how to handle existing groups with
349+
the same name. Specify one of two `IfAlreadyExistsOptions` variants:
350+
* `ignoreIfAlreadyExists` - do not attempt to create the group if a
351+
group with the same name already exists. Other operations for the
352+
UserGroupAction object will be performed.
353+
* `updateIfAlreadyExists` - update the description if it is different
354+
than the description currently applied to the user group
355+
* **Default**: `ignoreIfAlreadyExists`
356+
357+
Example:
358+
359+
```python
360+
group = UserGroupAction(group_name="Employees")
361+
group.create(description="A user group just for employees",
362+
option=IfAlreadyExistsOptions.updateIfAlreadyExists)
363+
```
364+
365+
### Update a Group
366+
367+
Updates are done using `UserGroupAction.update()`. Two optional
368+
parameters can be passed:
369+
370+
* `name` - the new name of the user group
371+
* `description` - the new description of the user group
372+
373+
Both parameters default to `None`. If either parameter is omitted,
374+
that field will not be updated. If neither parameter is specified,
375+
`update()` will throw an `ArgumentException`.
376+
377+
Example:
378+
379+
```python
380+
group = UserGroupAction(group_name="Employees")
381+
group.update(name="Employees and Contractors",
382+
description="Full-time Employees and Contractors")
383+
```
384+
385+
### Delete a Group
386+
387+
User groups can be deleted with `UserGroupAction.delete()`. The
388+
`delete()` method takes no parameters.
389+
390+
```python
391+
group = UserGroupAction(group_name="Employees and Contractors")
392+
group.delete()
393+
```
394+
395+
### Manage Users in Group
396+
397+
The User Management API supports the bulk management of users for a
398+
specific group. This functionality is exposed in the
399+
`UserGroupAction` methods `add_users()` and `remove_users()`.
400+
401+
A list of user IDs to add or remove must to provided to either method.
402+
403+
Add users example:
404+
405+
```python
406+
group = UserGroupAction(group_name="Employees and Contractors")
407+
408+
group.add_users(users=add_users)
409+
```
410+
411+
Remove users example:
412+
413+
```python
414+
group = UserGroupAction(group_name="Employees and Contractors")
415+
416+
group.remove_users(users=remove_users)
417+
```
418+
419+
### Manage Product Profiles for a Group
420+
421+
The `UserGroupAction` interface can also be used to manage the product
422+
profiles associated with a user group. Adding a product profile to a
423+
group will grant all members of that group access to the profile's
424+
associated product plan.
425+
426+
The `add_to_products()` method adds one or more product profiles to a
427+
user group. `remove_from_products()` removes one or more profiles from
428+
a user group.
429+
430+
Both methods take two parameters. These are mutually exclusive -
431+
either method will throw an `ArgumentError` if both parameters are
432+
specified (also, if neither are specified).
433+
434+
* `products` list of product profiles to add/remove
435+
* `all_products` boolean that will add/remove all product profiles to a
436+
user group
437+
438+
Add profile example:
439+
440+
```python
441+
group = UserGroupAction(group_name="Employees and Contractors")
442+
profiles_to_add = ["Default All Apps plan - 100 GB configuration",
443+
"Default Acrobat Pro DC configuration"]
444+
group.add_to_products(products=profiles_to_add)
445+
```
446+
447+
Remove profile example:
448+
449+
```python
450+
group = UserGroupAction(group_name="Employees and Contractors")
451+
profiles_to_remove = ["Default All Apps plan - 100 GB configuration",
452+
"Default Acrobat Pro DC configuration"]
453+
group.remove_from_products(products=profiles_to_add)
454+
```
455+
456+
## Step 3: Submit to the UMAPI server
457+
458+
`UserGroupAction` objects can be used with the same conn methods as
459+
`UserAction` objects. See "Step 3" in the User Management section of
460+
this document for more information.
461+
462+
Here is a complete example that creates a new user group and adds a
463+
list of users to it:
464+
465+
```python
466+
group = UserGroupAction(group_name="New User Group")
467+
group.create(description="A new user group",
468+
option=IfAlreadyExistsOptions.updateIfAlreadyExists)
469+
group.add_users(users=["[email protected]", "[email protected]"])
470+
471+
result = conn.execute_single(group)
472+
```

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
class MockResponse:
4141
def __init__(self, status=200, body=None, headers=None, text=None):
4242
self.status_code = status
43-
self.body = body if body else {}
43+
self.body = body if body is not None else {}
4444
self.headers = headers if headers else {}
4545
self.text = text if text else ""
4646

tests/test_connections.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from umapi_client import Connection
3232
from umapi_client import ArgumentError, UnavailableError, ServerError, RequestError
33-
from umapi_client import UserAction, GroupTypes, IdentityTypes, RoleTypes
33+
from umapi_client import UserAction, GroupTypes, IdentityTypes, RoleTypes, UserGroupAction
3434
from umapi_client import __version__ as umapi_version
3535
from umapi_client.auth import Auth
3636

@@ -451,3 +451,12 @@ def test_split_remove_all():
451451
'G10']}},
452452
{'add': {'product': ['G11']}}],
453453
'user': '[email protected]'}
454+
455+
456+
def test_split_group_action():
457+
user_template = six.text_type("user.{}@example.com")
458+
add_users = [user_template.format(n+1) for n in range(0, 25)]
459+
group = UserGroupAction(group_name="Test Group")
460+
group.add_users(users=add_users)
461+
assert group.maybe_split_groups(10) is True
462+
assert len(group.commands) == 3

tests/test_functional.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222

2323
import pytest
2424
import six
25+
import mock
2526

26-
from conftest import mock_connection_params
27-
from umapi_client import ArgumentError
27+
from conftest import mock_connection_params, MockResponse
28+
from umapi_client import ArgumentError, RequestError
2829
from umapi_client import Connection
2930
from umapi_client import IdentityTypes, GroupTypes, RoleTypes
3031
from umapi_client import UserAction, UserGroupAction
@@ -354,7 +355,7 @@ def test_delete_account_adobeid():
354355
def test_add_to_products():
355356
group = UserGroupAction(group_name="SampleUsers")
356357
group.add_to_products(products=["Photoshop", "Illustrator"])
357-
assert group.wire_dict() == {"do": [{"add": {"product": ["Photoshop", "Illustrator"]}}],
358+
assert group.wire_dict() == {"do": [{"add": {"productConfiguration": ["Photoshop", "Illustrator"]}}],
358359
"usergroup": "SampleUsers"}
359360

360361

@@ -374,7 +375,7 @@ def test_add_to_products_all_error():
374375
def test_remove_from_products():
375376
group = UserGroupAction(group_name="SampleUsers")
376377
group.remove_from_products(products=["Photoshop", "Illustrator"])
377-
assert group.wire_dict() == {"do": [{"remove": {"product": ["Photoshop", "Illustrator"]}}],
378+
assert group.wire_dict() == {"do": [{"remove": {"productConfiguration": ["Photoshop", "Illustrator"]}}],
378379
"usergroup": "SampleUsers"}
379380

380381

@@ -424,6 +425,57 @@ def test_remove_users_error():
424425
group.remove_users(users=[])
425426

426427

428+
def test_create_user_group():
429+
group = UserGroupAction(group_name="Test Group")
430+
group.create(description="Test Group Description")
431+
assert group.wire_dict() == {'do': [{'createUserGroup': {'option': 'ignoreIfAlreadyExists',
432+
'description': 'Test Group Description'}}],
433+
'usergroup': 'Test Group'}
434+
435+
436+
def test_create_user_group_error():
437+
group = UserGroupAction(group_name="Test Group")
438+
group.create(description="Test Group Description")
439+
with pytest.raises(ArgumentError):
440+
group.create()
441+
442+
443+
def test_invalid_user_group_name():
444+
group = UserGroupAction(group_name="_Invalid Group Name")
445+
with pytest.raises(ArgumentError):
446+
group.create()
447+
with pytest.raises(ArgumentError):
448+
group.update(name="_Another invalid group name")
449+
450+
451+
def test_long_user_group_name():
452+
long_group_name = """
453+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
454+
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
455+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
456+
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."""
457+
group = UserGroupAction(group_name=long_group_name)
458+
with pytest.raises(ArgumentError):
459+
group.create()
460+
with pytest.raises(ArgumentError):
461+
group.update(name=long_group_name)
462+
463+
464+
def test_update_user_group():
465+
group = UserGroupAction(group_name="Test Group")
466+
group.update(name="Renamed Test Group", description="Test Group Description")
467+
assert group.wire_dict() == {'do': [{'updateUserGroup': {'name': 'Renamed Test Group',
468+
'description': 'Test Group Description'}}],
469+
'usergroup': 'Test Group'}
470+
471+
472+
def test_delete_user_group():
473+
group = UserGroupAction("Test Group")
474+
group.delete()
475+
assert group.wire_dict() == {'do': [{'deleteUserGroup': {}}],
476+
'usergroup': 'Test Group'}
477+
478+
427479
def test_query_users():
428480
conn = Connection(**mock_connection_params)
429481
query = UsersQuery(conn)
@@ -432,3 +484,4 @@ def test_query_users():
432484
query = UsersQuery(conn, in_group="test", in_domain="test.com", direct_only=False)
433485
assert query.url_params == ["test"]
434486
assert query.query_params == {"directOnly": False, "domain": "test.com"}
487+

tests/test_live.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@
2828

2929
import umapi_client
3030

31-
# this test relies on a sensitive configuraition
31+
# PyCharm runs tests by default in the tests directory,
32+
# but setup.py runs them in the umapi_client directory,
33+
# so make sure we can run either way
34+
if os.getcwd().find("tests"):
35+
os.chdir("..")
36+
37+
# this test relies on a sensitive configuration
3238
config_file_name = "local/live_configuration.yaml"
3339
pytestmark = pytest.mark.skipif(not os.access(config_file_name, os.R_OK),
3440
reason="Live config file '{}' not found.".format(config_file_name))
@@ -43,7 +49,7 @@ def config():
4349
with open(config_file_name, "r") as f:
4450
conf_dict = yaml.load(f)
4551
creds = conf_dict["test_org"]
46-
conn = umapi_client.Connection(org_id=creds["org_id"], auth_dict=creds)
52+
conn = umapi_client.Connection(ims_host='ims-na1-stg1.adobelogin.com', user_management_endpoint='https://usermanagement-stage.adobe.io/v2/usermanagement', org_id=creds["org_id"], auth_dict=creds)
4753
return conn, conf_dict
4854

4955

@@ -79,14 +85,42 @@ def test_list_users(config):
7985
logging.info("Found %d users.", user_count)
8086

8187

88+
def test_list_user_groups(config):
89+
conn, params = config
90+
groups = umapi_client.UserGroupsQuery(connection=conn)
91+
group_count = 0
92+
for group in groups:
93+
name = group.get("name")
94+
admin_name = group.get("adminGroupName")
95+
member_count = group.get("userCount", 0)
96+
admin_count = int(group.get("adminCount", "-1"))
97+
logging.info("Group %s has %d members.", name, member_count)
98+
if admin_name:
99+
logging.info("Group %s has admin group %s with %d members.", name, admin_name, admin_count)
100+
# logging.info("Adding test user as admin.")
101+
# user = umapi_client.UserAction(id_type=params["test_user"]["type"],
102+
# email=params["test_user"]["email"],
103+
# username=params["test_user"]["username"])
104+
# user.add_to_groups([admin_name])
105+
# assert (0, 1, 1) == conn.execute_single(user, immediate=True)
106+
users = umapi_client.UsersQuery(connection=conn, in_group=admin_name)
107+
logging.info("Admin group %s has: %s", admin_name, users.all_results())
108+
assert member_count >= 0
109+
group_count += 1
110+
logging.info("Found %d groups.", group_count)
111+
groups.reload()
112+
group_count_2 = len(groups.all_results())
113+
assert group_count == group_count_2
114+
115+
82116
def test_list_groups(config):
83117
conn, params = config
84118
groups = umapi_client.GroupsQuery(connection=conn)
85119
group_count = 0
86120
for group in groups:
87121
name = group.get("groupName")
88122
member_count = group.get("memberCount", -1)
89-
logging.info("Group %s has %d members.", name, member_count)
123+
logging.info("Group %s has %d members: %s", name, member_count, group)
90124
assert member_count >= 0
91125
group_count += 1
92126
logging.info("Found %d groups.", group_count)
@@ -112,3 +146,24 @@ def test_rename_user(config):
112146
user.update(first_name="Rodney", last_name="Danger")
113147
user.update(first_name=params["test_user"]["firstname"], last_name=params["test_user"]["lastname"])
114148
assert (0, 1, 1) == conn.execute_single(user, immediate=True)
149+
150+
def test_create_user_group(config):
151+
conn, params = config
152+
usergroups = umapi_client.UserGroups(conn)
153+
groupName = "Test-Dummy-Group"
154+
groupID = usergroups.getGroupIDByName(groupName)
155+
if groupID:
156+
usergroups.delete(groupName)
157+
result = usergroups.create("Test-Dummy-Group")
158+
assert result.name == "Test-Dummy-Group"
159+
160+
def test_remove_user_group(config):
161+
conn, params = config
162+
usergroups = umapi_client.UserGroups(conn)
163+
groupName = "Test-Dummy-Group"
164+
groupID = usergroups.getGroupIDByName(groupName)
165+
if not groupID:
166+
result = usergroups.create(groupName)
167+
groupID = result.groupid
168+
usergroups.delete(groupID)
169+
assert usergroups.getGroupIDByName(groupName) == None

0 commit comments

Comments
 (0)