示例#1
0
def _get_okta_group_members(api_client: ApiClient, group_id: str) -> List[str]:
    """
    Get group members from Okta server
    :param api_client: Okta api client
    :param group_id: group to fetch members from
    :return: Array or group membership information
    """
    member_list: List[str] = []
    next_url = None

    while True:
        try:
            # https://developer.okta.com/docs/reference/api/groups/#list-group-members
            if next_url:
                paged_response = api_client.get(next_url)
            else:
                params = {
                    'limit': 1000,
                }
                paged_response = api_client.get_path(f'/{group_id}/users',
                                                     params)
        except OktaError as okta_error:
            logger.debug(
                f"Got error while going through list group member {okta_error}"
            )
            break

        member_list.append(paged_response.text)

        if not is_last_page(paged_response):
            next_url = paged_response.links.get("next").get("url")
        else:
            break

    return member_list
示例#2
0
    def get_paged_log_events(self, since=None, until=None, q=None, filter=None, limit=None, url=None):
        """Get a paged list of log events

        :param since: filters the lower time bound of the log events published property
        :type since: datetime or None
        :param until: filters the upper time bound of the log events published property
        :type until: datetime or None
        :param filter: filter expression that filters the results
        :type filter: str or None
        :param q: filters the log events results by one or more exact keywords
        :type q: str or None
        :rtype: list of dictionaries representing log events
        :param limit: The number of results returned in the response
        :type limit: int or None
        :param url: url that returns a list of log events
        :type url: str
        :rtype: PagedResults of log events in JSON format
        """
        if url:
            response = ApiClient.get(self, url)
        else:
            params = {
                'since': since,
                'until': until,
                'q': q,
                'filter': filter,
                'limit': limit,
            }
            response = ApiClient.get_path(self, '/', params=params)
        return PagedResults(response)
示例#3
0
    def get_paged_events(self,
                         limit=None,
                         start_date=None,
                         after=None,
                         filter_string=None,
                         url=None):
        """Get a paged list of Events

        :param limit: maximum number of events to return
        :type limit: int or None
        :param filter_string: string to filter events
        :type filter_string: str or None
        :param after: event id that filtering will resume after
        :type after: str or None
        :param url: url that returns a list of Event
        :type url: str or None
        :rtype: PagedResults of Event
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {
                'limit': limit,
                'startDate': start_date,
                'after': after,
                'filter': filter_string
            }
            response = ApiClient.get_path(self, '/', params=params)

        return PagedResults(response, Event)
示例#4
0
def _get_okta_groups(api_client: ApiClient) -> List[str]:
    """
    Get groups from Okta server
    :param api_client: Okta api client
    :return: Array of group information
    """
    group_list: List[str] = []
    next_url = None

    # SDK Bug
    # get_paged_groups returns User object instead of UserGroup

    while True:
        # https://developer.okta.com/docs/reference/api/groups/#list-groups
        if next_url:
            paged_response = api_client.get(next_url)
        else:
            params = {
                'limit': 10000,
            }
            paged_response = api_client.get_path('/', params)

        paged_results = PagedResults(paged_response, UserGroup)

        group_list.extend(paged_results.result)

        if not is_last_page(paged_response):
            next_url = paged_response.links.get("next").get("url")
        else:
            break

    return group_list
    def get_paged_groups(self,
                         limit=None,
                         filter_string=None,
                         after=None,
                         url=None):
        """Get a paged list of UserGroups

        :param limit: maximum number of groups to return
        :type limit: int or None
        :param filter_string: Filter expression for groups
        :type filter_string: str or None
        :param after: group id that filtering will resume after
        :type after: str
        :param url: url that returns a list of UserGroup
        :type url: str
        :rtype: PagedResults of UserGroup
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {'limit': limit, 'filter': filter_string, 'after': after}
            response = ApiClient.get_path(self, '/', params=params)

        return PagedResults(response, UserGroup)
示例#6
0
    def get_paged_users(self,
                        limit=None,
                        filter_string=None,
                        search=None,
                        after=None,
                        url=None):
        """Get a paged list of Users

        :param limit: maximum number of users to return
        :type limit: int or None
        :param filter_string: string to filter users
        :type filter_string: str or None
        :param after: user id that filtering will resume after
        :type after: str
        :param url: url that returns a list of User
        :type url: str
        :rtype: PagedResults of User
        """
        if url:
            response = ApiClient.get(self, url)
        else:
            params = {
                'limit': limit,
                'after': after,
                'filter': filter_string,
                'search': search
            }
            response = ApiClient.get_path(self, '/', params=params)
        return PagedResults(response, User)
示例#7
0
def _get_okta_applications(api_client: ApiClient) -> List[Dict]:
    """
    Get application data from Okta server
    :param app_client: api client
    :return: application data
    """
    app_list: List[Dict] = []

    next_url = None
    while True:
        try:
            # https://developer.okta.com/docs/reference/api/apps/#list-applications
            if next_url:
                paged_response = api_client.get(next_url)
            else:
                params = {
                    'limit': 500,
                }
                paged_response = api_client.get_path('/', params)
        except OktaError as okta_error:
            logger.debug(f"Got error while listing applications {okta_error}")
            break

        app_list.extend(json.loads(paged_response.text))

        if not is_last_page(paged_response):
            next_url = paged_response.links.get("next").get("url")
        else:
            break

    return app_list
示例#8
0
    def get_paged_users(self, limit=None, filter_string=None, after=None, url=None):
        """Get a paged list of Users

        :param limit: maximum number of users to return
        :type limit: int or None
        :param filter_string: string to filter users
        :type filter_string: str or None
        :param after: user id that filtering will resume after
        :type after: str
        :param url: url that returns a list of User
        :type url: str
        :rtype: PagedResults of User
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {
                'limit': limit,
                'after': after,
                'filter': filter_string
            }
            response = ApiClient.get_path(self, '/', params=params)

        return PagedResults(response, User)
示例#9
0
def get_okta_group_members(api_client: ApiClient, group_id: str) -> List[Dict]:
    """
    Get group members from Okta server
    :param api_client: Okta api client
    :param group_id: group to fetch members from
    :return: Array or group membership information
    """
    member_list: List[Dict] = []
    next_url = None

    while True:
        try:
            # https://developer.okta.com/docs/reference/api/groups/#list-group-members
            if next_url:
                paged_response = api_client.get(next_url)
            else:
                params = {
                    'limit': 1000,
                }
                paged_response = api_client.get_path(f'/{group_id}/users',
                                                     params)
        except OktaError:
            logger.error(
                f"OktaError while listing members of group {group_id}")
            raise

        member_list.extend(json.loads(paged_response.text))

        if not is_last_page(paged_response):
            next_url = paged_response.links.get("next").get("url")
        else:
            break

    return member_list
示例#10
0
    def get_paged_events(self, limit=None, start_date=None, after=None, filter_string=None, url=None):
        """Get a paged list of Events

        :param limit: maximum number of events to return
        :type limit: int or None
        :param filter_string: string to filter events
        :type filter_string: str or None
        :param after: event id that filtering will resume after
        :type after: str or None
        :param url: url that returns a list of Event
        :type url: str or None
        :rtype: PagedResults of Event
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {
                'limit': limit,
                'startDate': start_date,
                'after': after,
                'filter': filter_string
            }
            response = ApiClient.get_path(self, '/', params=params)

        return PagedResults(response, Event)
    def get_paged_app_instances(self, limit=None, filter_string=None, after=None, url=None):
        """Get a paged list of AppInstances

        :param limit: maximum number of apps to return
        :type limit: int or None
        :param filter_string: string to filter apps
        :type filter_string: str or None
        :param after: app id that filtering will resume after
        :type after: str
        :param url: url that returns a list of AppInstance
        :type url: str
        :rtype: PagedResults of AppInstance
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {
                'limit': limit,
                'after': after,
                'filter': filter_string
            }
            response = ApiClient.get_path(self, '/', params=params)

        return PagedResults(response, AppInstance)
示例#12
0
    def _get_aws_account_info(okta_org_url, okta_api_key, username):
        """ Call the Okta User API and process the results to return
        just the information we need for gimme_aws_creds"""
        # We need access to the entire JSON response from the Okta APIs, so we need to
        # use the low-level ApiClient instead of UsersClient and AppInstanceClient
        users_client = ApiClient(okta_org_url,
                                 okta_api_key,
                                 pathname='/api/v1/users')

        # Get User information
        try:
            result = users_client.get_path('/{0}'.format(username))
            user = result.json()
        except OktaError as e:
            if e.error_code == 'E0000007':
                print("Error: " + username + " was not found!",
                      file=sys.stderr)
                exit(1)
            else:
                print("Error: " + e.error_summary, file=sys.stderr)
                exit(1)

        try:
            # Get first page of results
            result = users_client.get_path('/{0}/appLinks'.format(user['id']))
            final_result = result.json()

            # Loop through other pages
            while 'next' in result.links:
                result = users_client.get(result.links['next']['url'])
                final_result = final_result + result.json()
            print("done\n", file=sys.stderr)
        except OktaError as e:
            if e.error_code == 'E0000007':
                print("Error: No applications found for " + username,
                      file=sys.stderr)
                exit(1)
            else:
                print("Error: " + e.error_summary, file=sys.stderr)
                exit(1)

        # Loop through the list of apps and filter it down to just the info we need
        app_list = []
        for app in final_result:
            # All AWS connections have the same app name
            if (app['appName'] == 'amazon_aws'):
                newAppEntry = {}
                newAppEntry['id'] = app['id']
                newAppEntry['name'] = app['label']
                newAppEntry['links'] = {}
                newAppEntry['links']['appLink'] = app['linkUrl']
                newAppEntry['links']['appLogo'] = app['logoUrl']
                app_list.append(newAppEntry)

        # Throw an error if we didn't get any accounts back
        if not app_list:
            print("No AWS accounts found.", file=sys.stderr)
            exit()

        return app_list
示例#13
0
    def push_verification_poll(self, url):
        """Poll for push verification

        :param url: push polling URL
        :type url: str
        :rtype: ActivationResponse
        """
        response = ApiClient.get(self, url)
        return Utils.deserialize(response.text, FactorVerificationResponse)
示例#14
0
    def get_paged_groups(self, limit=None, after=None, url=None):
        """Get a paged list of UserGroups

        :param limit: maximum number of groups to return
        :type limit: int or None
        :param after: group id that filtering will resume after
        :type after: str
        :param url: url that returns a list of UserGroup
        :type url: str
        :rtype: PagedResults of UserGroup
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {
                'limit': limit,
                'after': after
            }
            response = ApiClient.get_path(self, '/', params=params)

        return PagedResults(response, User)
示例#15
0
    def get_paged_group_members(self, gid, url=None, limit=None, after=None):
        """Get a paged list of users from a group

        :param gid: the group id
        :type gid: str
        :param limit: maximum number of users to return
        :type limit: int or None
        :param after: user id that filtering will resume after
        :type after: str
        :param url: url that returns a list of User
        :type url: str
        :rtype: PagedResults of User
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {'limit': limit, 'after': after}
            response = ApiClient.get_path(self,
                                          '/{0}/users'.format(gid),
                                          params=params)
        return PagedResults(response, User)
示例#16
0
    def get_app_paged_users(self, gid=None, limit=None, after=None, url=None):
        """Get a paged list of AppUsers of an app

        :param gid: the app id
        :type gid: str
        :param limit: maximum number of AppUser to return
        :type limit: int or None
        :param after: app id that filtering will resume after
        :type after: str
        :param url: url that returns a list of AppUser
        :type url: str
        :rtype: AppUser
        """
        if url:
            response = ApiClient.get(self, url)
        else:
            params = {
                'limit': limit,
                'after': after
            }
            response = ApiClient.get_path(self, '/{0}/users'.format(gid), params=params)

        return PagedResults(response, AppUser)
示例#17
0
def _get_application_assigned_users(api_client: ApiClient,
                                    app_id: str) -> List[str]:
    """
    Get users assigned to a specific application
    :param api_client: api client
    :param app_id: application id to get users from
    :return: Array of user data
    """
    app_users: List[str] = []

    next_url = None
    while True:
        try:
            # https://developer.okta.com/docs/reference/api/apps/#list-users-assigned-to-application
            if next_url:
                paged_response = api_client.get(next_url)
            else:
                params = {
                    'limit': 500,
                }
                paged_response = api_client.get_path(f'/{app_id}/users',
                                                     params)
        except OktaError as okta_error:
            logger.debug(
                f"Got error while going through list application assigned users {okta_error}"
            )
            break

        app_users.append(paged_response.text)

        if not is_last_page(paged_response):
            next_url = paged_response.links.get("next").get("url")
        else:
            break

    return app_users
示例#18
0
    def get_paged_app_targets_for_app_admin(self, uid, rid, url=None, limit=None):
        """Get a paged list of app targets for an APP_ADMIN role assignment

        :param uid: the user id
        :type uid: str
        :param rid: the APP_ADMIN role id
        :type rid: str
        :param url: url that returns a list of app targets
        :type url: str
        :param limit: maximum number of Apps to return
        :type limit: int or None
        :rtype: Array of CatalogApp
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {
                'limit': limit
            }
            url_path = '/{0}/roles/{1}/targets/catalog/apps'.format(uid, rid)
            response = ApiClient.get_path(self, url_path, params=params)
        # TODO: create Catalog App Model
        return PagedResults(response, AppInstance)
示例#19
0
def _get_application_assigned_groups(api_client: ApiClient,
                                     app_id: str) -> List[str]:
    """
    Get groups assigned to a specific application
    :param api_client: api client
    :param app_id: application id to get users from
    :return: Array of group id
    """
    app_groups: List[str] = []

    next_url = None

    while True:
        try:
            if next_url:
                paged_response = api_client.get(next_url)
            else:
                params = {
                    'limit': 500,
                }
                paged_response = api_client.get_path(f'/{app_id}/groups',
                                                     params)
        except OktaError as okta_error:
            logger.debug(
                f"Got error while going through list application assigned groups {okta_error}"
            )
            break

        app_groups.append(paged_response.text)

        if not is_last_page(paged_response):
            next_url = paged_response.links.get("next").get("url")
        else:
            break

    return app_groups
示例#20
0
    def get_paged_group_targets_for_user_admin(self, uid, rid, url=None, limit=None):
        """Get a paged list of group targets for an USER_ADMIN role assignment

        :param uid: the user id
        :type uid: str
        :param rid: the USER_ADMIN role id
        :type rid: str
        :param url: url that returns a list of group targets
        :type url: str
        :param limit: maximum number of Group to return
        :type limit: int or None
        :rtype: Array of UserGroup
        """
        if url:
            response = ApiClient.get(self, url)

        else:
            params = {
                'limit': limit
            }
            url_path = '/{0}/roles/{1}/targets/groups'.format(uid, rid)
            response = ApiClient.get_path(self, url_path, params=params)

        return PagedResults(response, UserGroup)
示例#21
0
def aws_account_info(event, context):
    # We need access to the entire JSON response from the Okta APIs, so we need to
    # use the low-level ApiClient instead of UsersClient and AppInstanceClient
    usersClient = ApiClient(os.environ['OKTA_ORG_URL'],
                            os.environ['OKTA_API_KEY'],
                            pathname='/api/v1/users')
    appClient = ApiClient(os.environ['OKTA_ORG_URL'],
                          os.environ['OKTA_API_KEY'],
                          pathname='/api/v1/apps')

    # Get User information
    username = event['requestContext']['authorizer']['principalId']
    try:
        result = usersClient.get_path('/{0}'.format(username))
        user = result.json()
    except OktaError as e:
        if e.error_code == 'E0000007':
            statusCode = 404
        else:
            statusCode = 500
        return {
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Credentials': True
            },
            "statusCode": statusCode,
            "body": e.error_summary
        }

    # Get a list of apps for this user and include extended info about the user
    params = {
        'limit': 200,
        'filter':
        'user.id+eq+%22' + user['id'] + '%22&expand=user%2F' + user['id']
    }

    try:
        # Get first page of results
        result = usersClient.get_path('/{0}/appLinks'.format(user['id']))
        final_result = result.json()

        # Loop through other pages
        while 'next' in result.links:
            result = appClient.get(result.links['next']['url'])
            final_result = final_result + result.json()
    except OktaError as e:
        if e.error_code == 'E0000007':
            statusCode = 404
        else:
            statusCode = 500
        return {
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Credentials': True
            },
            "statusCode": statusCode,
            "body": e.error_summary
        }

    # Loop through the list of apps and filter it down to just the info we need
    appList = []
    for app in final_result:
        # All AWS connections have the same app name
        if (app['appName'] == 'amazon_aws'):
            newAppEntry = {}
            newAppEntry['id'] = app['id']
            newAppEntry['name'] = app['label']
            newAppEntry['links'] = {}
            newAppEntry['links']['appLink'] = app['linkUrl']
            newAppEntry['links']['appLogo'] = app['logoUrl']
            appList.append(newAppEntry)

    response = {
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Credentials': True
        },
        "statusCode": 200,
        "body": json.dumps(appList)
    }

    return response
示例#22
0
class AuthClient(BaseAuthClient):
    def __init__(self, connection_config, reauth_time, auth_attrib) -> None:
        '''
        Args:
            connection_config (Dict):
                Parameters required to connect to the Okta API
            reauth_time (int):
                The min time in seconds to cache auth requests
            auth_attrib (str):
                The attribute of the user record that will be used to
                authenticate them.
        '''
        super().__init__(reauth_time, auth_attrib)
        connection_config['pathname'] = '/api/v1/users'

        self.usersclient = UsersClient(**connection_config)
        self.factorsclient = FactorsClient(**connection_config)
        self.apiclient = ApiClient(**connection_config)

        # Maintain a per user lookup for poll URL
        self.poll_url = {}

    def _get_okta_userid(self, username):
        user = self.usersclient.get_users(query=username, limit=1)

        try:
            return user[0].id
        except Exception as error:
            logging.error('Error getting username {}'.format(error))
            return None

    def _get_factors(self, userid):
        try:
            return self.factorsclient.get_lifecycle_factors(userid)
        except Exception as error:
            logging.error('Error getting factors {}'.format(error))
            return None

    def can_auth(self, user):
        # type: () -> bool
        # Check Okta user for a push factor.
        # Returns false is not available
        # Returns factor Id if it is
        # TODO: Add support for other types of auth (TOTP, etc).
        username = self._auth_attribute(user)
        if username is not False:
            logging.debug('Checking auth capabilities for {}'.format(username))

            okta_user_id = self._get_okta_userid(username)
            factors = self._get_factors(okta_user_id)
            if factors is not None:
                for factor in factors:
                    if factor.factorType == 'push':
                        return factor.id

        return False

    def auth(self, user, reason=None):
        # type: (str) -> None
        logging.debug('Sending Okta Push request for {}'.format(
            self._auth_attribute(user)))

        # Okta's SDK is broken!
        # https://github.com/okta/okta-sdk-python/issues/66
        # res = self.factorsclient.verify_factor(
        #    user_id=self.okta_user_id,
        #    user_factor_id=self.okta_push_factor_id
        # )
        # Implement our own call which actually works
        okta_user_id = self._get_okta_userid(self._auth_attribute(user))
        res = self.apiclient.post_path('/{0}/factors/{1}/verify'.format(
            okta_user_id, user._factor_id))
        try:
            res_obj = json.loads(res.text)
        except Exception as error:
            raise AuthException(error)

        self.poll_url[okta_user_id] = res_obj['_links']['poll']['href']
        user._last_auth_state = AuthStates.PENDING

    def auth_status(self, user):
        # type: () -> int
        okta_user_id = self._get_okta_userid(self._auth_attribute(user))

        if user._last_auth_state == AuthStates.PENDING:
            response = self.apiclient.get(self.poll_url[okta_user_id])
            response_obj = json.loads(response.text)
            res = response_obj['factorResult']
            if res != 'WAITING':
                if res == 'SUCCESS':
                    user._last_auth_state = AuthStates.AUTHORIZED
                    user._last_auth_time = datetime.now(tz=pytz.utc)
                else:
                    user._last_auth_state = AuthStates.DENIED
                    user._last_auth_time = datetime.min
        elif user._last_auth_state == AuthStates.AUTHORIZED:
            if not self._recently_authed(user):
                user._last_auth_state = AuthStates.NONE
        return user._last_auth_state

    def reset(self, user):
        okta_user_id = self._get_okta_userid(self._auth_attribute(user))
        self.poll_url.pop(okta_user_id, None)
        user._last_auth_state = AuthStates.NONE
示例#23
0
    def _get_aws_account_info(okta_org_url, okta_api_key, username):
        """ Call the Okta User and App APIs and process the results to return
        just the information we need for gimme_aws_creds"""
        # We need access to the entire JSON response from the Okta APIs, so we need to
        # use the low-level ApiClient instead of UsersClient and AppInstanceClient
        users_client = ApiClient(okta_org_url,
                                 okta_api_key,
                                 pathname='/api/v1/users')
        app_client = ApiClient(okta_org_url,
                               okta_api_key,
                               pathname='/api/v1/apps')

        # Get User information
        try:
            result = users_client.get_path('/{0}'.format(username))
            user = result.json()
        except OktaError as e:
            if e.error_code == 'E0000007':
                print("Error: " + username + " was not found!")
                exit(1)
            else:
                print("Error: " + e.error_summary)
                exit(1)

        # Get a list of apps for this user and include extended info about the user
        params = {
            'limit':
            50,
            'filter':
            'user.id+eq+%22' + user['id'] + '%22&expand=user%2F' + user['id']
        }

        try:
            # Get first page of results
            result = app_client.get_path('/', params=params)
            final_result = result.json()

            # Loop through other pages
            while 'next' in result.links:
                print('.', end='', flush=True)
                result = app_client.get(result.links['next']['url'])
                final_result = final_result + result.json()
            print("done\n")
        except OktaError as e:
            if e.error_code == 'E0000007':
                print("Error: No applications found for " + username)
                exit(1)
            else:
                print("Error: " + e.error_summary)
                exit(1)

        # Loop through the list of apps and filter it down to just the info we need
        app_list = []
        for app in final_result:
            # All AWS connections have the same app name
            if app['name'] == 'amazon_aws':
                new_app_entry = {
                    'id':
                    app['id'],
                    'name':
                    app['label'],
                    'identityProviderArn':
                    app['settings']['app']['identityProviderArn'],
                    'roles': []
                }
                # Build a list of the roles this user has access to
                for role in app['_embedded']['user']['profile']['samlRoles']:
                    role_info = {
                        'name':
                        role,
                        'arn':
                        re.sub(':saml-provider.*', ':role/' + role,
                               app['settings']['app']['identityProviderArn'])
                    }
                    # We can figure out the role ARN based on the ARN for the IdP
                    new_app_entry['roles'].append(role_info)
                new_app_entry['links'] = {}
                new_app_entry['links']['appLink'] = app['_links']['appLinks'][
                    0]['href']
                new_app_entry['links']['appLogo'] = app['_links']['logo'][0][
                    'href']
                app_list.append(new_app_entry)

        # Throw an error if we didn't get any accounts back
        if not app_list:
            print("No AWS accounts found.")
            exit()

        return app_list