Iterating the GitHub API (For users sans MFA)

Today I found myself auditing an organization’s users to see which have multifactor authentication enabled1. Since we have a not insignificant number of users, I wanted to write a quick script to automate it. Down the rabbit hole I go… and now I have a clean way of iterating across paginated GitHub API responses.

First, the full script:

#!/usr/bin/env python3

import requests
import os

try:
    token = os.environ['GITHUB_TOKEN']
except:
    print('$GITHUB_TOKEN must be set with proper permission')
    sys.exit(0)

headers = {'Authorization': 'token {}'.format(token)}

def api_iterator(endpoint):
    url = 'https://api.github.com' + endpoint

    while True:
        response = requests.get(url, headers = headers)
        yield from response.json()

        if 'next' in response.links:
            url = response.links['next']['url']
        else:
            return

The core of this script once again leans against the excellent Requests library for Python. It makes making simple requests and parsing the HTTP Link Header trivial. Also, yield from is pretty cool.

Basically, we use these instructions to create a GitHub access token. You’ll need at least the organization rights, I don’t have an exact list. Unfortunately, it doesn’t look like there is a way to do this with a username, password, and MFA token. I tried a few variations but it kept claiming that I wasn’t an owner of the organization. So it goes.

Now, if we wanted to use this for my original goal of finding users without MFA, you need the /orgs/:organization/members endpoint, with a specific filter:

endpoint = '/orgs/{}/members?filter=2fa_disabled'.format(organization)
for user in api_iterator(endpoint):
    print(user['login'])

Alternatively, you can use it just as easily to get all of your repositories (similar to what I did for my post on backing up GitHub repositories):

for repo in api_iterator('/user/repos'):
    print(repo['url'])

Cool. Hope it’s helpful!

The full source for the MFA version is also available on GitHub: missing-mfa.py


  1. As you should ↩︎