From 769785c2992e0985922fca148e84ee2cd4a05144 Mon Sep 17 00:00:00 2001 From: Justin Chung <20733699+justin13888@users.noreply.github.com> Date: Thu, 13 Oct 2022 17:52:01 -0400 Subject: [PATCH 1/5] Implement TUI support for multiple users in each position --- ceod/api/positions.py | 10 +++++++--- .../members/UpdateMemberPositionsTransaction.py | 13 ++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ceod/api/positions.py b/ceod/api/positions.py index a194565..b23af20 100644 --- a/ceod/api/positions.py +++ b/ceod/api/positions.py @@ -15,7 +15,10 @@ def get_positions(): positions = {} for user in ldap_srv.get_users_with_positions(): for position in user.positions: - positions[position] = user.uid + if position in positions: + positions[position] += f", {user.uid}" + else: + positions[position] = user.uid return positions @@ -29,9 +32,10 @@ def update_positions(): required = cfg.get('positions_required') available = cfg.get('positions_available') - # remove falsy values + # remove falsy values and parse multiple users in each position + # Example: "user1,user2, user3" -> ["user1","user2","user3"] body = { - positions: username for positions, username in body.items() + positions: username.replace(' ','').split(',') for positions, username in body.items() if username } diff --git a/ceod/transactions/members/UpdateMemberPositionsTransaction.py b/ceod/transactions/members/UpdateMemberPositionsTransaction.py index 32ac77e..56fff2d 100644 --- a/ceod/transactions/members/UpdateMemberPositionsTransaction.py +++ b/ceod/transactions/members/UpdateMemberPositionsTransaction.py @@ -2,6 +2,7 @@ from collections import defaultdict from typing import Dict from zope import component +from typing import Union from ..AbstractTransaction import AbstractTransaction from ceo_common.interfaces import ILDAPService, IConfig, IUser @@ -20,7 +21,7 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): 'subscribe_to_mailing_lists', ] - def __init__(self, positions_reversed: Dict[str, str]): + def __init__(self, positions_reversed: Dict[str, Union[str,list]]): # positions_reversed is position -> username super().__init__() self.ldap_srv = component.getUtility(ILDAPService) @@ -28,8 +29,14 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): # Reverse the dict so it's easier to use (username -> positions) self.positions = defaultdict(list) for position, username in positions_reversed.items(): - self.positions[username].append(position) - + if isinstance(username, str): + self.positions[username].append(position) + elif isinstance(username, list): + for user in username: + self.positions[user].append(position) + else: + raise TypeError("Username(s) under each position must either be a string or a list") + # a cached Dict of the Users who need to be modified (username -> User) self.users: Dict[str, IUser] = {} -- 2.39.5 From d16e7bb293cdfa730673b486d8b9c4969b4cbe72 Mon Sep 17 00:00:00 2001 From: Justin Chung <20733699+justin13888@users.noreply.github.com> Date: Thu, 13 Oct 2022 20:17:48 -0400 Subject: [PATCH 2/5] Fixed linting --- ceo/tui/controllers/SetPositionsController.py | 5 +++++ ceod/api/positions.py | 2 +- .../transactions/members/UpdateMemberPositionsTransaction.py | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ceo/tui/controllers/SetPositionsController.py b/ceo/tui/controllers/SetPositionsController.py index 17f8000..a3937ff 100644 --- a/ceo/tui/controllers/SetPositionsController.py +++ b/ceo/tui/controllers/SetPositionsController.py @@ -8,6 +8,10 @@ import ceo.tui.utils as tui_utils from ceo.tui.views import TransactionView from ceod.transactions.members import UpdateMemberPositionsTransaction +from ceo_common.logger_factory import logger_factory + +logger = logger_factory(__name__) + class SetPositionsController(Controller): def __init__(self, model, app): @@ -18,6 +22,7 @@ class SetPositionsController(Controller): for pos, field in self.view.position_fields.items(): if field.edit_text != '': body[pos] = field.edit_text + logger.info(body) model = TransactionModel( UpdateMemberPositionsTransaction.operations, 'POST', '/api/positions', json=body diff --git a/ceod/api/positions.py b/ceod/api/positions.py index b23af20..7bf7243 100644 --- a/ceod/api/positions.py +++ b/ceod/api/positions.py @@ -35,7 +35,7 @@ def update_positions(): # remove falsy values and parse multiple users in each position # Example: "user1,user2, user3" -> ["user1","user2","user3"] body = { - positions: username.replace(' ','').split(',') for positions, username in body.items() + positions: username.replace(' ', '').split(',') for positions, username in body.items() if username } diff --git a/ceod/transactions/members/UpdateMemberPositionsTransaction.py b/ceod/transactions/members/UpdateMemberPositionsTransaction.py index 56fff2d..7193e3e 100644 --- a/ceod/transactions/members/UpdateMemberPositionsTransaction.py +++ b/ceod/transactions/members/UpdateMemberPositionsTransaction.py @@ -21,7 +21,7 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): 'subscribe_to_mailing_lists', ] - def __init__(self, positions_reversed: Dict[str, Union[str,list]]): + def __init__(self, positions_reversed: Dict[str, Union[str, list]]): # positions_reversed is position -> username super().__init__() self.ldap_srv = component.getUtility(ILDAPService) @@ -36,7 +36,7 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): self.positions[user].append(position) else: raise TypeError("Username(s) under each position must either be a string or a list") - + # a cached Dict of the Users who need to be modified (username -> User) self.users: Dict[str, IUser] = {} -- 2.39.5 From 910449d67a09d04995f7666f7c38d4a70698c504 Mon Sep 17 00:00:00 2001 From: Justin Chung <20733699+justin13888@users.noreply.github.com> Date: Fri, 14 Oct 2022 13:21:12 -0400 Subject: [PATCH 3/5] Remove some logging code --- ceo/tui/controllers/SetPositionsController.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ceo/tui/controllers/SetPositionsController.py b/ceo/tui/controllers/SetPositionsController.py index a3937ff..b2ce36b 100644 --- a/ceo/tui/controllers/SetPositionsController.py +++ b/ceo/tui/controllers/SetPositionsController.py @@ -8,11 +8,6 @@ import ceo.tui.utils as tui_utils from ceo.tui.views import TransactionView from ceod.transactions.members import UpdateMemberPositionsTransaction -from ceo_common.logger_factory import logger_factory - -logger = logger_factory(__name__) - - class SetPositionsController(Controller): def __init__(self, model, app): super().__init__(model, app) @@ -22,7 +17,6 @@ class SetPositionsController(Controller): for pos, field in self.view.position_fields.items(): if field.edit_text != '': body[pos] = field.edit_text - logger.info(body) model = TransactionModel( UpdateMemberPositionsTransaction.operations, 'POST', '/api/positions', json=body -- 2.39.5 From 41454913737073d66eafe98ec063fb64a86b2dad Mon Sep 17 00:00:00 2001 From: Justin Chung <20733699+justin13888@users.noreply.github.com> Date: Fri, 14 Oct 2022 13:31:32 -0400 Subject: [PATCH 4/5] Fix linting error --- ceo/tui/controllers/SetPositionsController.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ceo/tui/controllers/SetPositionsController.py b/ceo/tui/controllers/SetPositionsController.py index b2ce36b..17f8000 100644 --- a/ceo/tui/controllers/SetPositionsController.py +++ b/ceo/tui/controllers/SetPositionsController.py @@ -8,6 +8,7 @@ import ceo.tui.utils as tui_utils from ceo.tui.views import TransactionView from ceod.transactions.members import UpdateMemberPositionsTransaction + class SetPositionsController(Controller): def __init__(self, model, app): super().__init__(model, app) -- 2.39.5 From dbe1ef4d097ce04d50347f47c641b5fac6e64139 Mon Sep 17 00:00:00 2001 From: Max Erenberg Date: Mon, 23 Jan 2023 02:15:13 -0500 Subject: [PATCH 5/5] use list to represent multiple users for a given position --- ceo/cli/positions.py | 15 +++- ceo/tui/controllers/GetPositionsController.py | 4 +- ceo/tui/controllers/SetPositionsController.py | 6 +- ceo/utils.py | 3 + ceod/api/positions.py | 41 ++++++---- .../UpdateMemberPositionsTransaction.py | 25 +++--- docker-compose.yml | 2 + docs/openapi.yaml | 57 +++++++------ docs/redoc-static.html | 27 ++++--- requirements.txt | 2 +- tests/ceo/cli/test_db_mysql.py | 14 +++- tests/ceo/cli/test_members.py | 4 +- tests/ceo/cli/test_positions.py | 80 +++++++++++++++++-- tests/ceod/api/test_positions.py | 53 +++++++----- 14 files changed, 224 insertions(+), 109 deletions(-) diff --git a/ceo/cli/positions.py b/ceo/cli/positions.py index 733e8c0..b302a16 100644 --- a/ceo/cli/positions.py +++ b/ceo/cli/positions.py @@ -16,13 +16,22 @@ def positions(): def get(): resp = http_get('/api/positions') result = handle_sync_response(resp) - print_colon_kv(result.items()) + print_colon_kv([ + (position, ', '.join(usernames)) + for position, usernames in result.items() + ]) @positions.command(short_help='Update positions') def set(**kwargs): - body = {k.replace('_', '-'): v for k, v in kwargs.items()} - print_body = {k: v or '' for k, v in body.items()} + body = { + k.replace('_', '-'): v.replace(' ', '').split(',') if v else None + for k, v in kwargs.items() + } + print_body = { + k: ', '.join(v) if v else '' + for k, v in body.items() + } click.echo('The positions will be updated:') print_colon_kv(print_body.items()) click.confirm('Do you want to continue?', abort=True) diff --git a/ceo/tui/controllers/GetPositionsController.py b/ceo/tui/controllers/GetPositionsController.py index 3c40ec1..9c89410 100644 --- a/ceo/tui/controllers/GetPositionsController.py +++ b/ceo/tui/controllers/GetPositionsController.py @@ -19,8 +19,8 @@ class GetPositionsController(Controller): positions = tui_utils.handle_sync_response(resp, self) except Controller.RequestFailed: return - for pos, username in positions.items(): - self.model.positions[pos] = username + for pos, usernames in positions.items(): + self.model.positions[pos] = ','.join(usernames) def target(): self.view.flash_text.set_text('') diff --git a/ceo/tui/controllers/SetPositionsController.py b/ceo/tui/controllers/SetPositionsController.py index 17f8000..2669e7c 100644 --- a/ceo/tui/controllers/SetPositionsController.py +++ b/ceo/tui/controllers/SetPositionsController.py @@ -17,7 +17,7 @@ class SetPositionsController(Controller): body = {} for pos, field in self.view.position_fields.items(): if field.edit_text != '': - body[pos] = field.edit_text + body[pos] = field.edit_text.replace(' ', '').split(',') model = TransactionModel( UpdateMemberPositionsTransaction.operations, 'POST', '/api/positions', json=body @@ -37,8 +37,8 @@ class SetPositionsController(Controller): positions = tui_utils.handle_sync_response(resp, self) except Controller.RequestFailed: return - for pos, username in positions.items(): - self.model.positions[pos] = username + for pos, usernames in positions.items(): + self.model.positions[pos] = ','.join(usernames) def target(): self.view.flash_text.set_text('') diff --git a/ceo/utils.py b/ceo/utils.py index abc96bb..b053f7d 100644 --- a/ceo/utils.py +++ b/ceo/utils.py @@ -87,6 +87,9 @@ def space_colon_kv(pairs: List[Tuple[str, str]]) -> List[str]: maxlen = max(len(key) for key, val in pairs) for key, val in pairs: if key != '': + if not val: + lines.append(key + ':') + continue prefix = key + ': ' else: # assume this is a continuation from the previous line diff --git a/ceod/api/positions.py b/ceod/api/positions.py index 7bf7243..14bb9c0 100644 --- a/ceod/api/positions.py +++ b/ceod/api/positions.py @@ -2,6 +2,7 @@ from flask import Blueprint, request from zope import component from .utils import authz_restrict_to_syscom, create_streaming_response +from ceo_common.errors import BadRequest from ceo_common.interfaces import ILDAPService, IConfig from ceod.transactions.members import UpdateMemberPositionsTransaction @@ -15,10 +16,9 @@ def get_positions(): positions = {} for user in ldap_srv.get_users_with_positions(): for position in user.positions: - if position in positions: - positions[position] += f", {user.uid}" - else: - positions[position] = user.uid + if position not in positions: + positions[position] = [] + positions[position].append(user.uid) return positions @@ -34,22 +34,29 @@ def update_positions(): # remove falsy values and parse multiple users in each position # Example: "user1,user2, user3" -> ["user1","user2","user3"] - body = { - positions: username.replace(' ', '').split(',') for positions, username in body.items() - if username - } + position_to_usernames = {} + for position, usernames in body.items(): + if not usernames: + continue + if type(usernames) is list: + position_to_usernames[position] = usernames + elif type(usernames) is str: + position_to_usernames[position] = usernames.replace(' ', '').split(',') + else: + raise BadRequest('usernames must be a list or comma-separated string') - for position in body.keys(): + # check for duplicates (i.e. one username specified twice in the same list) + for usernames in position_to_usernames.values(): + if len(usernames) != len(set(usernames)): + raise BadRequest('username may only be specified at most once for a position') + + for position in position_to_usernames.keys(): if position not in available: - return { - 'error': f'unknown position: {position}' - }, 400 + raise BadRequest(f'unknown position: {position}') for position in required: - if position not in body: - return { - 'error': f'missing required position: {position}' - }, 400 + if position not in position_to_usernames: + raise BadRequest(f'missing required position: {position}') - txn = UpdateMemberPositionsTransaction(body) + txn = UpdateMemberPositionsTransaction(position_to_usernames) return create_streaming_response(txn) diff --git a/ceod/transactions/members/UpdateMemberPositionsTransaction.py b/ceod/transactions/members/UpdateMemberPositionsTransaction.py index 7193e3e..8221f6a 100644 --- a/ceod/transactions/members/UpdateMemberPositionsTransaction.py +++ b/ceod/transactions/members/UpdateMemberPositionsTransaction.py @@ -1,8 +1,7 @@ from collections import defaultdict -from typing import Dict +from typing import Dict, List from zope import component -from typing import Union from ..AbstractTransaction import AbstractTransaction from ceo_common.interfaces import ILDAPService, IConfig, IUser @@ -21,21 +20,19 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): 'subscribe_to_mailing_lists', ] - def __init__(self, positions_reversed: Dict[str, Union[str, list]]): + def __init__(self, position_to_usernames: Dict[str, List[str]]): # positions_reversed is position -> username super().__init__() self.ldap_srv = component.getUtility(ILDAPService) # Reverse the dict so it's easier to use (username -> positions) self.positions = defaultdict(list) - for position, username in positions_reversed.items(): - if isinstance(username, str): - self.positions[username].append(position) - elif isinstance(username, list): - for user in username: - self.positions[user].append(position) + for position, usernames in position_to_usernames.items(): + if isinstance(usernames, list): + for username in usernames: + self.positions[username].append(position) else: - raise TypeError("Username(s) under each position must either be a string or a list") + raise TypeError("Username(s) under each position must be a list") # a cached Dict of the Users who need to be modified (username -> User) self.users: Dict[str, IUser] = {} @@ -49,7 +46,7 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): mailing_lists = cfg.get('auxiliary mailing lists_exec') # position -> username - new_positions_reversed = {} # For returning result + new_position_to_usernames = {} # For returning result # retrieve User objects and cache them for username in self.positions: @@ -71,7 +68,9 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): self.old_positions[username] = old_positions for position in new_positions: - new_positions_reversed[position] = username + if position not in new_position_to_usernames: + new_position_to_usernames[position] = [] + new_position_to_usernames[position].append(username) yield 'update_positions_ldap' # update exec group in LDAP @@ -104,7 +103,7 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): else: yield 'subscribe_to_mailing_lists' - self.finish(new_positions_reversed) + self.finish(new_position_to_usernames) def rollback(self): if 'update_exec_group_ldap' in self.finished_operations: diff --git a/docker-compose.yml b/docker-compose.yml index 59d246c..4db5345 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,8 @@ x-common: &common image: python:3.9-bullseye volumes: - .:$PWD:z + security_opt: + - label:disable environment: FLASK_APP: ceod.api FLASK_ENV: development diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 3bbe59d..c98a8e4 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -61,9 +61,9 @@ paths: program: $ref: "#/components/schemas/Program" terms: - $ref: "#/components/schemas/Terms" + $ref: "#/components/schemas/TermsOrNumTerms" non_member_terms: - $ref: "#/components/schemas/NonMemberTerms" + $ref: "#/components/schemas/TermsOrNumTerms" forwarding_addresses: $ref: "#/components/schemas/ForwardingAddresses" responses: @@ -161,17 +161,11 @@ paths: - type: object properties: terms: - type: array - description: Terms for which this user will be a member - items: - $ref: "#/components/schemas/Term" + $ref: "#/components/schemas/TermsOrNumTerms" - type: object properties: non_member_terms: - type: array - description: Terms for which this user will be a club rep - items: - $ref: "#/components/schemas/Term" + $ref: "#/components/schemas/TermsOrNumTerms" example: {"terms": ["f2021"]} responses: "200": @@ -383,11 +377,14 @@ paths: schema: type: object additionalProperties: - type: string + type: array + description: list of usernames + items: + type: string example: - president: user0 - vice-president: user1 - sysadmin: user2 + president: ["user1"] + vice-president: ["user2", "user3"] + sysadmin: ["user4"] treasurer: post: tags: ['positions'] @@ -404,11 +401,18 @@ paths: schema: type: object additionalProperties: - type: string + oneOf: + - type: string + description: username or comma-separated list of usernames + - type: array + description: list of usernames + items: + type: string example: - president: user0 - vice-president: user1 - sysadmin: user2 + president: user1 + vice-president: user2, user3 + secretary: ["user4", "user5"] + sysadmin: ["user6"] treasurer: responses: "200": @@ -422,7 +426,7 @@ paths: {"status": "in progress", "operation": "update_positions_ldap"} {"status": "in progress", "operation": "update_exec_group_ldap"} {"status": "in progress", "operation": "subscribe_to_mailing_list"} - {"status": "completed", "result": "OK"} + {"status": "completed", "result": {"president": ["user1"],"vice-president": ["user2", "user3"],"secretary": ["user4". "user5"],"sysadmin": ["user6"]}} "400": description: Failed content: @@ -883,14 +887,15 @@ components: example: MAT/Mathematics Computer Science Terms: type: array - description: Terms for which this user was a member - items: - $ref: "#/components/schemas/Term" - NonMemberTerms: - type: array - description: Terms for which this user was a club rep + description: List of terms items: $ref: "#/components/schemas/Term" + TermsOrNumTerms: + oneOf: + - type: integer + description: number of additional terms to add + example: 1 + - $ref: "#/components/schemas/Terms" LoginShell: type: string description: Login shell @@ -939,7 +944,7 @@ components: terms: $ref: "#/components/schemas/Terms" non_member_terms: - $ref: "#/components/schemas/NonMemberTerms" + $ref: "#/components/schemas/Terms" forwarding_addresses: $ref: "#/components/schemas/ForwardingAddresses" groups: diff --git a/docs/redoc-static.html b/docs/redoc-static.html index 23f935e..0aeeea6 100644 --- a/docs/redoc-static.html +++ b/docs/redoc-static.html @@ -1854,13 +1854,14 @@ h1:hover > .sc-crzoAE::before,h2:hover > .iUxAWq::before,.iUxAWq:hover::before{v data-styled.g14[id="sc-crzoAE"]{content:"iUxAWq,"}/*!sc*/ .dvcDrG{height:18px;width:18px;min-width:18px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/ .iPqByX{height:1.3em;width:1.3em;min-width:1.3em;vertical-align:middle;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/ +.hGHhhO{height:18px;width:18px;min-width:18px;vertical-align:middle;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/ .dqYXmg{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/ .dqYXmg polygon{fill:#1d8127;}/*!sc*/ .bRmrKA{height:20px;width:20px;min-width:20px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(0);-ms-transform:rotateZ(0);transform:rotateZ(0);}/*!sc*/ .bRmrKA polygon{fill:white;}/*!sc*/ .dVWHLw{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/ .dVWHLw polygon{fill:#d41f1c;}/*!sc*/ -data-styled.g15[id="sc-dIsUp"]{content:"dvcDrG,iPqByX,dqYXmg,bRmrKA,dVWHLw,"}/*!sc*/ +data-styled.g15[id="sc-dIsUp"]{content:"dvcDrG,iPqByX,hGHhhO,dqYXmg,bRmrKA,dVWHLw,"}/*!sc*/ .iwcKgn{border-left:1px solid #7c7cbb;box-sizing:border-box;position:relative;padding:10px 10px 10px 0;}/*!sc*/ @media screen and (max-width:50rem){.iwcKgn{display:block;overflow:hidden;}}/*!sc*/ tr:first-of-type > .sc-hBMUJo,tr.last > .iwcKgn{border-left-width:0;background-position:top left;background-repeat:no-repeat;background-size:1px 100%;}/*!sc*/ @@ -2068,6 +2069,11 @@ data-styled.g53[id="sc-cOifOu"]{content:"hlhNtL,"}/*!sc*/ data-styled.g54[id="sc-Arkif"]{content:"jojbRz,"}/*!sc*/ .dSaTNC{margin-top:15px;}/*!sc*/ data-styled.g57[id="sc-jgPyTC"]{content:"dSaTNC,"}/*!sc*/ +.hTttpy button{background-color:transparent;border:0;outline:0;font-size:13px;font-family:Courier,monospace;cursor:pointer;padding:0;color:#333333;}/*!sc*/ +.hTttpy button:focus{font-weight:600;}/*!sc*/ +.hTttpy .sc-dIsUp{height:1.1em;width:1.1em;}/*!sc*/ +.hTttpy .sc-dIsUp polygon{fill:#666;}/*!sc*/ +data-styled.g58[id="sc-gSYDnn"]{content:"hTttpy,"}/*!sc*/ .jWaWWE{vertical-align:middle;font-size:13px;line-height:20px;}/*!sc*/ data-styled.g59[id="sc-laZMeE"]{content:"jWaWWE,"}/*!sc*/ .jrLlAa{color:rgba(102,102,102,0.9);}/*!sc*/ @@ -2259,11 +2265,9 @@ in real time.

sn
string (UserSN)

Last name

given_name
string (UserGivenName)

First name

program
string (Program)

Academic program

-
terms
Array of strings (Terms)

Terms for which this user was a member

-
non_member_terms
Array of strings (NonMemberTerms)

Terms for which this user was a club rep

-
forwarding_addresses
Array of strings <email> (ForwardingAddresses)

Forwarding addresses in ~/.forward

+
integer or Array of Terms (strings) (TermsOrNumTerms)
integer or Array of Terms (strings) (TermsOrNumTerms)
forwarding_addresses
Array of strings <email> (ForwardingAddresses)

Forwarding addresses in ~/.forward

Responses

Request samples

Content type
application/json
{
  • "uid": "ctdalek",
  • "cn": "Calum Dalek",
  • "sn": "Dalek",
  • "given_name": "Calum",
  • "program": "MAT/Mathematics Computer Science",
  • "terms": [
    ],
  • "non_member_terms": [
    ],
  • "forwarding_addresses": [
    ]
}

Response samples

Content type
text/plain
{"status": "in progress", "operation": "add_user_to_ldap"}
+

Request samples

Content type
application/json
{
  • "uid": "ctdalek",
  • "cn": "Calum Dalek",
  • "sn": "Dalek",
  • "given_name": "Calum",
  • "program": "MAT/Mathematics Computer Science",
  • "terms": 1,
  • "non_member_terms": 1,
  • "forwarding_addresses": [
    ]
}

Response samples

Content type
text/plain
{"status": "in progress", "operation": "add_user_to_ldap"}
 {"status": "in progress", "operation": "add_group_to_ldap"}
 {"status": "in progress", "operation": "add_user_to_kerberos"}
 {"status": "in progress", "operation": "create_home_dir"}
@@ -2286,8 +2290,7 @@ in real time.

{"status": "completed", "result": "OK"}

Renew a user

Add member or non-member terms to a user

Authorizations:
GSSAPIAuth
path Parameters
username
required
string

username of the user to renew

-
Request Body schema: application/json
One of
terms
Array of strings (Term)

Terms for which this user will be a member

-

Responses

Request Body schema: application/json
One of
integer or Array of Terms (strings) (TermsOrNumTerms)

Responses

Request samples

Content type
application/json
{
  • "terms": [
    ]
}

Response samples

Content type
application/json
{
  • "terms_added": [
    ]
}

Reset a user's password

Sets a user's password to a randomly generated string, and returns it. The user will be prompted to set a new password on their next login.

Authorizations:
GSSAPIAuth
path Parameters
username
required
string

username of the user whose password will be reset

@@ -2402,17 +2405,17 @@ If the namespace already exists, the certificate inside the kubeconfig will be r
Authorizations:
GSSAPIAuth

Responses

Response samples

Content type
application/json
{
  • "status": "OK",
  • "kubeconfig": "string"
}

positions

Show current positions

Shows the list of positions and members holding them.

Authorizations:
GSSAPIAuth

Responses

Response samples

Content type
application/json
{
  • "president": "user0",
  • "vice-president": "user1",
  • "sysadmin": "user2",
  • "treasurer": null
}

Update positions

Update members for each positions. Members not specified in the parameters will be removed from the position and unsubscribed from the exec's mailing list. New position holders will be subscribed to the mailing list.

+

Response samples

Content type
application/json
{
  • "president": [
    ],
  • "vice-president": [
    ],
  • "sysadmin": [
    ],
  • "treasurer": null
}

Update positions

Update members for each positions. Members not specified in the parameters will be removed from the position and unsubscribed from the exec's mailing list. New position holders will be subscribed to the mailing list.

Authorizations:
GSSAPIAuth
Request Body schema: application/json

New position holders

-
property name*
additional property
string

Responses

additional property
string or Array of strings

Responses

Request samples

Content type
application/json
{
  • "president": "user0",
  • "vice-president": "user1",
  • "sysadmin": "user2",
  • "treasurer": null
}

Response samples

Content type
text/plain
{"status": "in progress", "operation": "update_positions_ldap"}
+

Request samples

Content type
application/json
{
  • "president": "user1",
  • "vice-president": "user2, user3",
  • "secretary": [
    ],
  • "sysadmin": [
    ],
  • "treasurer": null
}

Response samples

Content type
text/plain
{"status": "in progress", "operation": "update_positions_ldap"}
 {"status": "in progress", "operation": "update_exec_group_ldap"}
 {"status": "in progress", "operation": "subscribe_to_mailing_list"}
-{"status": "completed", "result": "OK"}
+{"status": "completed", "result": {"president": ["user1"],"vice-president": ["user2", "user3"],"secretary": ["user4". "user5"],"sysadmin": ["user6"]}}