Skip to content

Instantly share code, notes, and snippets.

@vitalyisaev2
Last active April 26, 2024 05:02
Show Gist options
  • Save vitalyisaev2/215f890e75252cd36794221c2debf365 to your computer and use it in GitHub Desktop.
Save vitalyisaev2/215f890e75252cd36794221c2debf365 to your computer and use it in GitHub Desktop.
Script for obtaining Gitlab API Personal Access Token
#!/usr/bin/python3
"""
Script that creates Personal Access Token for Gitlab API;
Tested with:
- Gitlab Community Edition 10.1.4
- Gitlab Enterprise Edition 12.6.2
- Gitlab Enterprise Edition 13.4.4
"""
import sys
import requests
from urllib.parse import urljoin
from bs4 import BeautifulSoup
endpoint = "http://localhost:10080"
root_route = urljoin(endpoint, "/")
sign_in_route = urljoin(endpoint, "/users/sign_in")
pat_route = urljoin(endpoint, "/profile/personal_access_tokens")
login = "root"
password = "password"
def find_csrf_token(text):
soup = BeautifulSoup(text, "lxml")
token = soup.find(attrs={"name": "csrf-token"})
param = soup.find(attrs={"name": "csrf-param"})
data = {param.get("content"): token.get("content")}
return data
def obtain_csrf_token():
r = requests.get(root_route)
token = find_csrf_token(r.text)
return token, r.cookies
def sign_in(csrf, cookies):
data = {
"user[login]": login,
"user[password]": password,
"user[remember_me]": 0,
"utf8": "✓"
}
data.update(csrf)
r = requests.post(sign_in_route, data=data, cookies=cookies)
token = find_csrf_token(r.text)
return token, r.history[0].cookies
def obtain_personal_access_token(name, expires_at, csrf, cookies):
data = {
"personal_access_token[expires_at]": expires_at,
"personal_access_token[name]": name,
"personal_access_token[scopes][]": "api",
"utf8": "✓"
}
data.update(csrf)
r = requests.post(pat_route, data=data, cookies=cookies)
soup = BeautifulSoup(r.text, "lxml")
token = soup.find('input', id='created-personal-access-token').get('value')
return token
def main():
csrf1, cookies1 = obtain_csrf_token()
print("root", csrf1, cookies1)
csrf2, cookies2 = sign_in(csrf1, cookies1)
print("sign_in", csrf2, cookies2)
name = sys.argv[1]
expires_at = sys.argv[2]
token = obtain_personal_access_token(name, expires_at, csrf2, cookies2)
print(token)
if __name__ == "__main__":
main()
@vikas027
Copy link

I have dockerized this script in an alpine based container after adding all available scopes. Maybe someone finds it useful.

@colin-nolan
Copy link

@vitalyisaev2 please may you add a license to this snippet (perhaps MIT)?

@vitalyisaev2
Copy link
Author

@colin-nolan sorry, I missed your comment, it seems like there is no interface to add license to snippets, so please use it as a public domain.

@GCSBOSS
Copy link

GCSBOSS commented Sep 16, 2019

Hey! I'm trying to implement the same in NodeJS. Can you tell me if the 'requests' is following redirects?

@vitalyisaev2
Copy link
Author

@liu316484231
Copy link

awesome work, i implement this in a go version https://gist.github.com/liu316484231/0e02025cb507788b1d3be351006e599a

@vitalyisaev2
Copy link
Author

@liu316484231 good job!

@cawwot
Copy link

cawwot commented Jan 16, 2020

Still working in 12.6.2 Enterprise Edition. Thanks so much for this 🙏

@vitalyisaev2
Copy link
Author

@cawwot thanks for clarification!

@yannispgs
Copy link

Still working in 13.4.4 Enterprise Edition, great job !

@vitalyisaev2
Copy link
Author

@Sinayn34 thanks for this information! I've update list of tested versions.

It's surprising that Gitlab was not able to implement this feature for at least 3 major releases.

@IllanRULDACUNHA
Copy link

IllanRULDACUNHA commented Feb 17, 2021

Hello,

I just tested the code with Python 3.8.3 and 3.9.1, GitLab Enterprise Edition version 12.7.5, and it doesn't not work for me. I get an AttributeError:
data = {param.get("content"): token.get("content")} AttributeError: 'NoneType' object has no attribute 'get'

Do you have an idea of where it comes from?

Thank you much for your excellent work.

Good day to you.

@MiVents
Copy link

MiVents commented Feb 24, 2021

@IllanRULDACUNHA

I experienced the same issue. I extended this script with an authenticity_token and a change in the url.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Import Modules
import os
import sys
import requests
import argparse
from urllib.parse import urljoin
from bs4 import BeautifulSoup

# Variables
endpoint = os.environ['GITLAB_URL']
if 'ssh://' in endpoint:
    endpoint = "http://" + endpoint.split("@")[1]
login = os.environ['GITLAB_ADMIN_USER']
password = os.environ['GITLAB_ADMIN_PASSWD']
scopes = {'personal_access_token[scopes][]': [
    'api', 'sudo', 'read_user', 'read_repository']}
root_route = urljoin(endpoint, "/")
sign_in_route = urljoin(endpoint, "/users/sign_in")
pat_route = urljoin(endpoint, "/-/profile/personal_access_tokens")


# Methods
def find_csrf_token(text):
    soup = BeautifulSoup(text, "lxml")
    token = soup.find(attrs={"name": "csrf-token"})
    param = soup.find(attrs={"name": "csrf-param"})
    data = {param.get("content"): token.get("content")}
    return data


def obtain_csrf_token():
    r = requests.get(root_route)
    token = find_csrf_token(r.text)
    return token, r.cookies


def obtain_authenticity_token(cookies):
    r = requests.get(pat_route, cookies=cookies)
    soup = BeautifulSoup(r.text, "lxml")
    token = soup.find('input', attrs={'name':'authenticity_token', 'type':'hidden'}).get('value')
    return token


def sign_in(csrf, cookies):
    data = {
        "user[login]": login,
        "user[password]": password,
        "user[remember_me]": 0,
        "utf8": "✓"
    }
    data.update(csrf)
    r = requests.post(sign_in_route, data=data, cookies=cookies)
    token = find_csrf_token(r.text)
    return token, r.history[0].cookies


def obtain_personal_access_token(name, expires_at, csrf, cookies, authenticity_token):
    data = {
        "personal_access_token[expires_at]": expires_at,
        "personal_access_token[name]": name,
        "authenticity_token": authenticity_token,
        "utf8": "✓"
    }
    data.update(scopes)
    data.update(csrf)
    r = requests.post(pat_route, data=data, cookies=cookies)
    soup = BeautifulSoup(r.text, "lxml")
    token = soup.find('input', id='created-personal-access-token').get('value')
    return token


def main():
    csrf1, cookies1 = obtain_csrf_token()
    csrf2, cookies2 = sign_in(csrf1, cookies1)

    name = sys.argv[1]
    expires_at = sys.argv[2]
    authenticity_token = obtain_authenticity_token(cookies2)
    token = obtain_personal_access_token(name, expires_at, csrf2, cookies2, authenticity_token)
    print(token)


if __name__ == "__main__":
    main()

@IllanRULDACUNHA
Copy link

I still have the same error, @MiVents

data = {param.get("content"): token.get("content")} AttributeError: 'NoneType object has no attribute' get'

@vitalyisaev2
Copy link
Author

vitalyisaev2 commented Feb 26, 2021

Hi guys, thank you for discussion. I see that the problem is still relevant in more recent versions of Gitlab, so I decided to create a full-fledged repository to give developers opportunity to contribute and maintain newer versions of this script. I think that we need to develop a little library that will provide simple API to obtain tokens.

https://github.com/vitalyisaev2/gitlab_token

If anybody wants to be a maintainer, please drop me a line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment