Skip to content

Commit

Permalink
Bulk invite users in a particular group
Browse files Browse the repository at this point in the history
Bump version to 0.0.9
  • Loading branch information
bitonio committed Mar 4, 2022
1 parent 357ec9e commit bdaf3eb
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 12 deletions.
83 changes: 72 additions & 11 deletions bin/akamai-mfa
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ https://learn.akamai.com/en-us/webhelp/enterprise-mfa/akamai-mfa-logs-from-splun
"""

from ctypes import ArgumentError
import logging
from nis import match
import requests
import hashlib
import hmac
Expand All @@ -41,7 +43,7 @@ import csv


#: cli-mfa version, see also cli.json
__VERSION__ = "0.0.8"
__VERSION__ = "0.0.9"
#: Log formatting aligned with other CLIs
LOG_FMT = '%(asctime)s [%(levelname)s] %(threadName)s %(message)s'
#: Near real-time, 30s ago is the most recent by default
Expand All @@ -61,13 +63,23 @@ class cli():
"""

@staticmethod
def write(s):
print(s)
def print(s):
sys.stdout.write("%s\n" % s)
sys.stdout.flush()

@staticmethod
def print_error(s):
sys.stderr.write("%s\n" % s)
sys.stderr.flush()

@staticmethod
def current_command():
return "akamai mfa " + " ".join(sys.argv[1:])

@staticmethod
def exit(code):
sys.exit(code)

class MFAConfig():
"""
Manage CLI MFA input parameters
Expand Down Expand Up @@ -102,6 +114,18 @@ class MFAConfig():
eventparser.add_argument("--noreceipt", default=False, action="store_true",
help="Discard the receipt attribute to save log space")

# User sub parser, will replace the loaduserparser down the road
user_parser = subparsers.add_parser('users', help="User operations (search, import, etc...)")
# userparser.add_argument("action", choices=['search', 'import', 'invite'])
user_action_parser = user_parser.add_subparsers(dest="action", help='User operations (search, import, etc...)')

# usersearch_parser = user_action_parser.add_parser("search", help="Search users")
# usersearch_parser.add_argument('-g', '--group', dest="group", help='Limit search to users in this group')
# usersearch_parser.add_argument(dest='filter', default="*", help="Pattern to search user, default is *")
invite_parser = user_action_parser.add_parser("invite", help="Send enrollement invite over email")
invite_parser.add_argument('-g', '--group', dest="group", help='Send invite to member of this group')

# ad-hoc implementation to support MFA customers
loaduserparser = subparsers.add_parser('importusers', help="Import users from a CSV file")
loaduserparser.add_argument("--file", "-f", help="CSV file used as input")
loaduserparser.add_argument("--ignore-header", "-i", dest="ignore_header", default=False, action="store_true",
Expand Down Expand Up @@ -286,15 +310,28 @@ class IdentityManagementAPI(BaseAPI):
def list_groups(self):
"""
Fetch the list of groups visible in Akamai MFA.
<internal>
https://pushzero-staging.akamai.com/api/v1/open_api.html#get-/api/v1/control/groups
You will need to get all of the groups currently in the system in order to manage
the “group name” -> “group id” lookup since all apis related to groups operate on
their ids, not their names.
</internal>
"""
return self.get("/api/v1/control/groups")

def group_id(self, group_name):
"""
Return Group ID for a given group name
If the group name is not found or API return more than one result, return None
Args:
group_name (string): Group name
Returns:
string: ID of the group found
"""
group_info = self.get("/api/v1/control/groups", params={'name': group_name})
matches = group_info.get('result', {}).get('page', [])
match_count = len(matches)
if match_count != 1:
return None
else:
return matches[0].get('id')

def create_group(self, group_name, group_summary=None):
"""Create a new group in MFA backend."""
payload = {
Expand Down Expand Up @@ -358,20 +395,37 @@ class IdentityManagementAPI(BaseAPI):
count_group_existing += 1
logger.debug(f"Group {g} was already present, not added.")
logger.debug("Final groups: %s" % groups_map)
cli.write(f"{count_group_added} group(s) added, {count_group_existing} group(s) were already existing")
cli.print(f"{count_group_added} group(s) added, {count_group_existing} group(s) were already existing")

# Second, bulk user insertion
new_users_response = self.create_users(new_users)
logger.debug("new_users: %s" % new_users_response)
count_user_added = len(new_users_response.get('result', {}).get('created', []))
count_user_exist = len(new_users_response.get('result', {}).get('existing', []))
cli.write(f"{count_user_added} user(s) added, {count_user_exist} user(s) where already existing and left unchanged")
cli.print(f"{count_user_added} user(s) added, {count_user_exist} user(s) where already existing and left unchanged")

# Third, associate user to group
for new_user in new_users_response.get('result', {}).get('created', []):
group_id = reverse_groups_map[user_groupname_map[new_user.get('username')]]
self.associate_users_to_group([new_user.get('id')], group_id)

def enroll_users(self, group_name):
"""
Sends off emails to users in a list of groups.
"""
if not isinstance(group_name, str):
raise ArgumentError("groups must be an string")

group_id = self.group_id(group_name)
if group_id is None:
cli.print_error("Group %s not found." % group_name)
cli.exit(2)
payload = {'exclude_enrolled_users': True}
payload["groups"] = [group_id]
cli.print("Sending enrollment email to ununrolled users in group %s..." % group_name)
response = self.post("/api/v1/control/email/enroll_users", json=payload)
logger.debug(response)


if __name__ == "__main__":

Expand All @@ -394,6 +448,13 @@ if __name__ == "__main__":
sys.exit(0)
elif config.command == 'event':
EventAPI.pull_events()
elif config.command == 'users':
identity = IdentityManagementAPI(config, config.mfa_api_signing_key, config.mfa_api_integration_id)
if config.action == "invite":
identity.enroll_users(config.group)
else:
cli.write("not supported")
cli.exit(1)
elif config.command == 'importusers':
logger.debug("starting import...")
identity = IdentityManagementAPI(config, config.mfa_api_signing_key, config.mfa_api_integration_id)
Expand Down
2 changes: 1 addition & 1 deletion cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"commands": [
{
"name": "mfa",
"version": "0.0.8",
"version": "0.0.9",
"description": "Akamai CLI for MFA"
}
]
Expand Down

0 comments on commit bdaf3eb

Please sign in to comment.