Skip to content

Commit a3d8d43

Browse files
author
Rasmus Welander
committed
implement client for groups
1 parent e7e9f00 commit a3d8d43

File tree

5 files changed

+407
-0
lines changed

5 files changed

+407
-0
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- Support for common lock operations (set lock, get lock, unlock, ...).
2121
- Support for common share operations (create share, update share, delete share, ...).
2222
- Support for common user operations (get user, find users, get user groups, ...).
23+
- support for common group operations (get group, find group, has member, ...).
2324
- Support for restoring files through checkpoints (restore file version, list checkpoints).
2425
- Support for applications (open in app, list app providers).
2526
- Authentication and authorization handling.
@@ -302,6 +303,24 @@ res = client.user.get_user_by_claim("username", "rwelande")
302303

303304
```
304305

306+
### Group example
307+
```python
308+
# get_group_by_claim (username)
309+
res = client.group.get_group_by_claim(client.auth.get_token(), "username", "rwelande")
310+
311+
# get_group
312+
res = client.group.get_group(client.auth.get_token(), "https://auth.cern.ch/auth/realms/cern", "asdoiqwe")
313+
314+
# has_member
315+
res = client.group.has_member(client.auth.get_token(), "somegroup", "rwelande", "https://auth.cern.ch/auth/realms/cern")
316+
317+
# get_members
318+
res = client.group.get_members(client.auth.get_token(), "somegroup", "https://auth.cern.ch/auth/realms/cern")
319+
320+
# find_groups
321+
res = client.group.find_groups(client.auth.get_token(), "rwel")
322+
```
323+
305324
### App Example
306325
```python
307326
# list_app_providers

cs3client/cs3client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from .file import File
1515
from .user import User
16+
from .group import Group
1617
from .share import Share
1718
from .statuscodehandler import StatusCodeHandler
1819
from .app import App
@@ -48,6 +49,7 @@ def __init__(self, config: ConfigParser, config_category: str, log: logging.Logg
4849
self.file: File = File(self._config, self._log, self._gateway, self._status_code_handler)
4950
self.user: User = User(self._config, self._log, self._gateway, self._status_code_handler)
5051
self.app: App = App(self._config, self._log, self._gateway, self._status_code_handler)
52+
self.group: Group = Group(self._config, self._log, self._gateway, self._status_code_handler)
5153
self.checkpoint: Checkpoint = Checkpoint(
5254
self._config, self._log, self._gateway, self._status_code_handler
5355
)

cs3client/group.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
group.py
3+
4+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
5+
Emails: rasmus.oscar.welander@cern.ch, diogo.castro@cern.ch, giuseppe.lopresti@cern.ch
6+
Last updated: 08/12/2025
7+
"""
8+
9+
import logging
10+
import cs3.identity.group.v1beta1.resources_pb2 as cs3igr
11+
import cs3.identity.group.v1beta1.group_api_pb2 as cs3ig
12+
import cs3.identity.user.v1beta1.resources_pb2 as cs3iur
13+
from cs3.gateway.v1beta1.gateway_api_pb2_grpc import GatewayAPIStub
14+
15+
from .config import Config
16+
from .statuscodehandler import StatusCodeHandler
17+
18+
19+
class Group:
20+
"""
21+
Group class to handle group related API calls with CS3 Gateway API.
22+
"""
23+
24+
def __init__(
25+
self,
26+
config: Config,
27+
log: logging.Logger,
28+
gateway: GatewayAPIStub,
29+
status_code_handler: StatusCodeHandler,
30+
) -> None:
31+
"""
32+
Initializes the Group class with logger, auth, and gateway stub,
33+
34+
:param log: Logger instance for logging.
35+
:param gateway: GatewayAPIStub instance for interacting with CS3 Gateway.
36+
:param auth: An instance of the auth class.
37+
"""
38+
self._log: logging.Logger = log
39+
self._gateway: GatewayAPIStub = gateway
40+
self._config: Config = config
41+
self._status_code_handler: StatusCodeHandler = status_code_handler
42+
43+
def get_group(self, auth_token: tuple, opaque_id, idp) -> cs3igr.Group:
44+
"""
45+
Get the group information provided the opaque_id.
46+
47+
:param opaque_id: Opaque group id.
48+
:return: Group information.
49+
:raises: return NotFoundException (Group not found)
50+
:raises: AuthenticationException (Operation not permitted)
51+
:raises: UnknownException (Unknown error)
52+
"""
53+
req = cs3ig.GetGroupRequest(group_id=cs3igr.GroupId(opaque_id=opaque_id, idp=idp))
54+
res = self._gateway.GetGroup(request=req, metadata=[auth_token])
55+
self._status_code_handler.handle_errors(res.status, "get group", f'opaque_id="{opaque_id}"')
56+
self._log.debug(f'msg="Invoked GetGroup" opaque_id="{res.group.id.opaque_id}" trace="{res.status.trace}"')
57+
return res.group
58+
59+
def get_group_by_claim(self, auth_token: tuple, claim, value) -> cs3igr.Group:
60+
"""
61+
Get the group information provided the claim and value.
62+
63+
:param claim: Claim to search for.
64+
:param value: Value to search for.
65+
:return: Group information.
66+
:raises: NotFoundException (Group not found)
67+
:raises: AuthenticationException (Operation not permitted)
68+
:raises: UnknownException (Unknown error)
69+
"""
70+
req = cs3ig.GetGroupByClaimRequest(claim=claim, value=value, skip_fetching_members=False)
71+
res = self._gateway.GetGroupByClaim(request=req, metadata=[auth_token])
72+
self._status_code_handler.handle_errors(res.status, "get group by claim", f'claim="{claim}" value="{value}"')
73+
self._log.debug(f'msg="Invoked GetGroupByClaim" opaque_id="{res.group.id.opaque_id}" trace="{res.status.trace}"')
74+
return res.group
75+
76+
def has_member(self, auth_token: tuple, group_opaque_id, user_opaque_id, idp) -> bool:
77+
"""
78+
Check if a user is a member of a group.
79+
80+
:param group_opaque_id: Group opaque id.
81+
:param user_opaque_id: User opaque id.
82+
:return: True if the user is a member of the group, False otherwise.
83+
:raises: NotFoundException (Group not found)
84+
:raises: AuthenticationException (Operation not permitted)
85+
:raises: UnknownException (Unknown error)
86+
"""
87+
req = cs3ig.HasMemberRequest(group_id=cs3igr.GroupId(opaque_id=group_opaque_id, idp=idp), user_id=cs3iur.UserId(opaque_id=user_opaque_id, idp=idp))
88+
res = self._gateway.HasMember(request=req, metadata=[auth_token])
89+
self._status_code_handler.handle_errors(res.status, "has member", f'group_id="{group_opaque_id}" user_id="{user_opaque_id}"')
90+
self._log.debug(f'msg="Invoked HasMember" group_id="{group_opaque_id}" user_id="{user_opaque_id}" trace="{res.status.trace}"')
91+
return res.ok
92+
93+
def get_members(self, auth_token: tuple, opaque_id, idp) -> list[str]:
94+
"""
95+
Get the groups the user is a part of.
96+
97+
:param opaque_id: Opaque group id.
98+
:return: A list of the groups the user is part of.
99+
:raises: NotFoundException (User not found)
100+
:raises: AuthenticationException (Operation not permitted)
101+
:raises: UnknownException (Unknown error)
102+
"""
103+
req = cs3ig.GetMembersRequest(group_id=cs3igr.GroupId(idp=idp, opaque_id=opaque_id))
104+
res = self._gateway.GetUserGroups(request=req, metadata=[auth_token])
105+
self._status_code_handler.handle_errors(res.status, "get user groups", f'opaque_id="{opaque_id}"')
106+
self._log.debug(f'msg="Invoked GetUserGroups" opaque_id="{opaque_id}" trace="{res.status.trace}"')
107+
return res.groups
108+
109+
def find_groups(self, auth_token: tuple, filters) -> list[cs3igr.Group]:
110+
"""
111+
Find a group based on a filter.
112+
113+
:param auth_token: tuple in the form ('x-access-token', <token>) (see auth.get_token/auth.check_token)
114+
:param filters: Filters to search for.
115+
:return: a list of group(s).
116+
:raises: NotFoundException (Group not found)
117+
:raises: AuthenticationException (Operation not permitted)
118+
:raises: UnknownException (Unknown error)
119+
"""
120+
req = cs3ig.FindGroupsRequest(filters=filters)
121+
res = self._gateway.FindGroups(request=req, metadata=[auth_token])
122+
self._status_code_handler.handle_errors(res.status, "find groups")
123+
self._log.debug(f'msg="Invoked FindGroups" filter="{filter}" trace="{res.status.trace}"')
124+
return res.groups

examples/group_api_example.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
group_api_example.py
3+
4+
Example script to demonstrate the usage of the CS3Client class.
5+
6+
7+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
8+
Emails: rasmus.oscar.welander@cern.ch, diogo.castro@cern.ch, giuseppe.lopresti@cern.ch
9+
Last updated: 08/12/2025
10+
"""
11+
12+
import logging
13+
import configparser
14+
from cs3client.cs3client import CS3Client
15+
from cs3client.auth import Auth
16+
17+
config = configparser.ConfigParser()
18+
with open("default.conf") as fdef:
19+
config.read_file(fdef)
20+
log = logging.getLogger(__name__)
21+
22+
client = CS3Client(config, "cs3client", log)
23+
auth = Auth(client)
24+
# Set the client id (can also be set in the config)
25+
auth.set_client_id("<your_client_id_here>")
26+
# Set client secret (can also be set in config)
27+
auth.set_client_secret("<your_client_secret_here>")
28+
# Checks if token is expired if not return ('x-access-token', <token>)
29+
# if expired, request a new token from reva
30+
auth_token = auth.get_token()
31+
32+
# OR if you already have a reva token
33+
# Checks if token is expired if not return (x-access-token', <token>)
34+
# if expired, throws an AuthenticationException (so you can refresh your reva token)
35+
token = "<your_reva_token>"
36+
auth_token = Auth.check_token(token)
37+
38+
39+
res = None
40+
41+
42+
# get_group_by_claim (username)
43+
res = client.group.get_group_by_claim(client.auth.get_token(), "username", "rwelande")
44+
if res is not None:
45+
print(res)
46+
47+
# get_group
48+
res = client.group.get_group(client.auth.get_token(), "https://auth.cern.ch/auth/realms/cern", "asdoiqwe")
49+
50+
if res is not None:
51+
print(res)
52+
53+
# has_member
54+
res = client.group.has_member(client.auth.get_token(), "somegroup", "rwelande", "https://auth.cern.ch/auth/realms/cern")
55+
56+
if res is not None:
57+
print(res)
58+
59+
# get_members
60+
res = client.group.get_members(client.auth.get_token(), "somegroup", "https://auth.cern.ch/auth/realms/cern")
61+
if res is not None:
62+
print(res)
63+
64+
# find_groups
65+
res = client.group.find_groups(client.auth.get_token(), "rwel")
66+
if res is not None:
67+
print(res)

0 commit comments

Comments
 (0)