From af51847d8992e1e9f89a95c2d85df571d0ba79e0 Mon Sep 17 00:00:00 2001
From: Pierre-Yves Chibon <pingou@pingoured.fr>
Date: Mar 22 2017 08:57:29 +0000
Subject: [PATCH 1/6] Add support for admin API token to pagure-admin


---

diff --git a/pagure/cli/admin.py b/pagure/cli/admin.py
index b292e9f..a4280ee 100644
--- a/pagure/cli/admin.py
+++ b/pagure/cli/admin.py
@@ -10,6 +10,7 @@
 from __future__ import print_function
 
 import argparse
+import datetime
 import logging
 import os
 
@@ -21,7 +22,7 @@ if 'PAGURE_CONFIG' not in os.environ \
 import pagure.exceptions
 import pagure.lib
 import pagure.lib.git
-from pagure import (SESSION, generate_user_key_files)
+from pagure import (SESSION, APP, generate_user_key_files)
 
 
 _log = logging.getLogger(__name__)
@@ -57,6 +58,50 @@ def parse_arguments():
         help='Generate a new hook token for every project in this instance')
     parser_hook_token.set_defaults(func=do_generate_hook_token)
 
+    # Admin token actions
+    parser_admin_token = subparsers.add_parser(
+        'admin-token',
+        help='Manages the admin tokens for this instance')
+
+    subsubparsers = parser_admin_token.add_subparsers(title='actions')
+
+    # List admin token
+    parser_admin_list_token = subsubparsers.add_parser(
+        'list',  help="List the API admin token")
+    parser_admin_list_token.add_argument(
+        '--user',
+        help="User to associate or associated with the token")
+    parser_admin_list_token.add_argument(
+        '--token', help="API token")
+    parser_admin_list_token.add_argument(
+        '--active', default=False, action='store_true',
+        help="Only list active API token")
+    parser_admin_list_token.add_argument(
+        '--expired', default=False, action='store_true',
+        help="Only list expired API token")
+    parser_admin_token.set_defaults(func=do_list_admin_token)
+
+    # Info about admin token
+    parser_admin_info_token = subsubparsers.add_parser(
+        'info',  help="Provide some information about a specific API token")
+    parser_admin_info_token.add_argument(
+        'token', help="API token")
+    parser_admin_info_token.set_defaults(func=do_info_admin_token)
+
+    # Expire admin token
+    parser_admin_expire_token = subsubparsers.add_parser(
+        'expire',  help="Expire a specific API token")
+    parser_admin_expire_token.add_argument(
+        'token', help="API token")
+    parser_admin_expire_token.set_defaults(func=do_expire_admin_token)
+
+    # Create admin token
+    parser_admin_create_token = subsubparsers.add_parser(
+        'create',  help="Create a new API token")
+    parser_admin_create_token.add_argument(
+        'user', help="User to associate with the token")
+    parser_admin_create_token.set_defaults(func=do_create_admin_token)
+
     return parser.parse_args()
 
 
@@ -67,7 +112,7 @@ def _ask_confirmation():
     return action.lower() in ['y', 'yes']
 
 
-def do_generate_acl():
+def do_generate_acl(_):
     """ Regenerate the gitolite ACL file. """
     cmd = pagure.lib.git._get_gitolite_command()
     if not cmd:
@@ -80,7 +125,7 @@ def do_generate_acl():
         print('Gitolite ACLs updated')
 
 
-def do_refresh_ssh():
+def do_refresh_ssh(_):
     """ Regenerate the user key files. """
     print('Do you want to re-generate all the ssh keys for every user in '
         'the database? (Depending on your instance this may take a while '
@@ -101,6 +146,86 @@ def do_generate_hook_token():
         print('Hook token all re-generated')
 
 
+def do_list_admin_token(args):
+    """ List the admin token. """
+    _log.debug('user:           %s', args.user)
+    _log.debug('token:          %s', args.token)
+    _log.debug('active:         %s', args.active)
+    _log.debug('expire:         %s', args.expired)
+
+    acls = APP.config['ADMIN_API_ACLS']
+    tokens = pagure.lib.search_token(
+        SESSION, acls, active=args.active, expired=args.expired)
+    for token in tokens:
+        print('%s -- %s -- %s' % (
+            token.id, token.user.user, token.expiration))
+    if not tokens:
+        print('No admin tokens found')
+
+
+def do_info_admin_token(args):
+    """ Print out information about the specified API token. """
+    _log.debug('token:          %s', args.token)
+
+    acls = APP.config['ADMIN_API_ACLS']
+    token = pagure.lib.search_token(SESSION, acls, token=args.token)
+    if not token:
+        raise pagure.exceptions.PagureException('No such admin token found')
+
+    print('%s -- %s -- %s' % (
+        token.id, token.user.user, token.expiration))
+    print('ACLs:')
+    for acl in token.acls:
+        print('  - %s' % acl.name)
+
+
+def do_expire_admin_token(args):
+    """ Expire a specific admin token. """
+    _log.debug('token:          %s', args.token)
+
+    acls = APP.config['ADMIN_API_ACLS']
+    token = pagure.lib.search_token(SESSION, acls, token=args.token)
+    if not token:
+        raise pagure.exceptions.PagureException('No such admin token found')
+
+    print('%s -- %s -- %s' % (
+        token.id, token.user.user, token.expiration))
+    print('ACLs:')
+    for acl in token.acls:
+        print('  - %s' % acl.name)
+
+    print('Do you really want to expire this API token?')
+    if _ask_confirmation():
+        token.expiration = datetime.datetime.utcnow()
+        SESSION.add(token)
+        SESSION.commit()
+        print('Token expired')
+
+
+def do_create_admin_token(args):
+    """ Create a new admin token. """
+    _log.debug('user:          %s', args.user)
+    # Validate user first
+    user_obj = pagure.lib.get_user(SESSION, args.user)
+
+    acls_list = APP.config['ADMIN_API_ACLS']
+    for idx, acl in enumerate(acls_list):
+        print('%s.  %s' % (idx, acl))
+
+    print('Which ACLs do you want to associated with this token?')
+    acls = raw_input('(Conna separated list): ')
+    acls_idx = [int(acl.strip()) for acl in acls.split(',')]
+    acls = [acls_list[acl] for acl in acls_idx]
+
+    print('ACLs selected:')
+    for idx, acl in enumerate(acls_idx):
+        print('%s.  %s' % (acls_idx[idx], acls[idx]))
+
+    print('Do you want to create this API token?')
+    if _ask_confirmation():
+        print(pagure.lib.add_token_to_user(SESSION, None, acls, args.user))
+
+
 def main():
     """ Start of the application. """
 
@@ -114,7 +239,7 @@ def main():
     # Act based on the arguments given
     return_code = 0
     try:
-        args.func()
+        args.func(args)
     except KeyboardInterrupt:
         print("\nInterrupted by user.")
         return_code = 1
diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py
index f01dd5f..43ad605 100644
--- a/pagure/lib/__init__.py
+++ b/pagure/lib/__init__.py
@@ -4218,3 +4218,55 @@ def get_obj_access(session, project_obj, obj):
         )
 
     return query.first()
+
+
+def search_token(
+        session, acls, user=None, token=None, active=False, expired=False):
+    ''' Searches the API tokens corresponding to the criterias specified.
+
+    :arg session: the session to use to connect to the database.
+    :arg acls: List of the ACL associated with these API tokens
+    :arg user: restrict the API tokens to this given user
+    :arg token: restrict the API tokens to this specified token (if it
+        exists)
+    '''
+    query = session.query(
+        model.Token
+    ).filter(
+        model.Token.id == model.TokenAcl.token_id
+    ).filter(
+        model.TokenAcl.acl_id == model.ACL.id
+    )
+
+    if isinstance(acls, list):
+        query = query.filter(
+            model.ACL.name.in_(acls)
+        )
+    else:
+        query = query.filter(
+            model.ACL.name == acls
+        )
+
+    if user:
+        query = query.filter(
+            model.Token.user_id == model.User.id
+        ).filter(
+            model.User.user == user
+        )
+
+    if active:
+        query = query.filter(
+            model.Token.expiration > datetime.datetime.utcnow()
+        )
+    elif expired:
+        query = query.filter(
+            model.Token.expiration <= datetime.datetime.utcnow()
+        )
+
+    if token:
+        query = query.filter(
+            model.Token.id == token
+        )
+        return query.first()
+    else:
+        return query.all()

From f4ba432e91b2c663bcd8c8f06f1278eb41c1fde7 Mon Sep 17 00:00:00 2001
From: Pierre-Yves Chibon <pingou@pingoured.fr>
Date: Mar 22 2017 08:57:29 +0000
Subject: [PATCH 2/6] Small flake8 clean up to pagure/cli


---

diff --git a/pagure/cli/admin.py b/pagure/cli/admin.py
index a4280ee..b6bb983 100644
--- a/pagure/cli/admin.py
+++ b/pagure/cli/admin.py
@@ -13,16 +13,17 @@ import argparse
 import datetime
 import logging
 import os
+import sys
 
 if 'PAGURE_CONFIG' not in os.environ \
         and os.path.exists('/etc/pagure/pagure.cfg'):
     print('Using configuration file `/etc/pagure/pagure.cfg`')
     os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg'
 
-import pagure.exceptions
-import pagure.lib
-import pagure.lib.git
-from pagure import (SESSION, APP, generate_user_key_files)
+import pagure.exceptions  # noqa
+import pagure.lib  # noqa
+import pagure.lib.git  # noqa
+from pagure import (SESSION, APP, generate_user_key_files)  # noqa
 
 
 _log = logging.getLogger(__name__)
@@ -36,7 +37,7 @@ def parse_arguments():
     parser.add_argument(
         '--debug', default=False, action='store_true',
         help='Increase the verbosity of the information displayed')
-    parser.set_defaults(func=lambda a, k : print(parser.format_help()))
+    parser.set_defaults(func=lambda a, k: print(parser.format_help()))
 
     subparsers = parser.add_subparsers(title='actions')
 
@@ -118,7 +119,8 @@ def do_generate_acl(_):
     if not cmd:
         raise pagure.exceptions.PagureException(
             '/!\ un-able to generate the right gitolite command')
-    print('Do you want to re-generate the gitolite.conf file then '
+    print(
+        'Do you want to re-generate the gitolite.conf file then '
         'calling: %s' % cmd)
     if _ask_confirmation():
         pagure.lib.git.generate_gitolite_acls()
@@ -127,7 +129,8 @@ def do_generate_acl(_):
 
 def do_refresh_ssh(_):
     """ Regenerate the user key files. """
-    print('Do you want to re-generate all the ssh keys for every user in '
+    print(
+        'Do you want to re-generate all the ssh keys for every user in '
         'the database? (Depending on your instance this may take a while '
         'and result in an outage while it lasts)')
     if _ask_confirmation():
@@ -138,7 +141,8 @@ def do_refresh_ssh(_):
 
 def do_generate_hook_token():
     """ Regenerate the hook_token for each projects in the DB. """
-    print('Do you want to re-generate all the hook token for every user in '
+    print(
+        'Do you want to re-generate all the hook token for every user in '
         'the database? This will break every web-hook set-up on this '
         'instance. You should only ever run this for a security issue')
     if _ask_confirmation():
@@ -206,7 +210,7 @@ def do_create_admin_token(args):
     """ Create a new admin token. """
     _log.debug('user:          %s', args.user)
     # Validate user first
-    user_obj = pagure.lib.get_user(SESSION, args.user)
+    pagure.lib.get_user(SESSION, args.user)
 
     acls_list = APP.config['ADMIN_API_ACLS']
     for idx, acl in enumerate(acls_list):

From b22dbfbe85538b7f3b671b09789cebbf30ce7d10 Mon Sep 17 00:00:00 2001
From: Pierre-Yves Chibon <pingou@pingoured.fr>
Date: Mar 22 2017 08:57:29 +0000
Subject: [PATCH 3/6] Improve the docstrings in pagure-admin as advised by @bowlofeggs


---

diff --git a/pagure/cli/admin.py b/pagure/cli/admin.py
index b6bb983..4dc45f4 100644
--- a/pagure/cli/admin.py
+++ b/pagure/cli/admin.py
@@ -107,14 +107,20 @@ def parse_arguments():
 
 
 def _ask_confirmation():
-    ''' Ask to confirm an action
+    ''' Ask to confirm an action.
     '''
     action = raw_input('Do you want to continue? [y/N]')
     return action.lower() in ['y', 'yes']
 
 
 def do_generate_acl(_):
-    """ Regenerate the gitolite ACL file. """
+    """ Regenerate the gitolite ACL file.
+
+
+    :arg _: the argparse object returned by ``parse_arguments()``, which is
+        ignored as there are no argument to pass to this action.
+
+    """
     cmd = pagure.lib.git._get_gitolite_command()
     if not cmd:
         raise pagure.exceptions.PagureException(
@@ -128,7 +134,12 @@ def do_generate_acl(_):
 
 
 def do_refresh_ssh(_):
-    """ Regenerate the user key files. """
+    """ Regenerate the user key files.
+
+    :arg _: the argparse object returned by ``parse_arguments()``, which is
+        ignored as there are no argument to pass to this action.
+
+    """
     print(
         'Do you want to re-generate all the ssh keys for every user in '
         'the database? (Depending on your instance this may take a while '
@@ -139,8 +150,13 @@ def do_refresh_ssh(_):
         do_generate_acl()
 
 
-def do_generate_hook_token():
-    """ Regenerate the hook_token for each projects in the DB. """
+def do_generate_hook_token(_):
+    """ Regenerate the hook_token for each projects in the DB.
+
+    :arg _: the argparse object returned by ``parse_arguments()``, which is
+        ignored as there are no argument to pass to this action.
+
+    """
     print(
         'Do you want to re-generate all the hook token for every user in '
         'the database? This will break every web-hook set-up on this '
@@ -151,7 +167,11 @@ def do_generate_hook_token():
 
 
 def do_list_admin_token(args):
-    """ List the admin token. """
+    """ List the admin token.
+
+    :arg args: the argparse object returned by ``parse_arguments()``.
+
+    """
     _log.debug('user:           %s', args.user)
     _log.debug('token:          %s', args.token)
     _log.debug('active:         %s', args.active)
@@ -168,7 +188,11 @@ def do_list_admin_token(args):
 
 
 def do_info_admin_token(args):
-    """ Print out information about the specified API token. """
+    """ Print out information about the specified API token.
+
+    :arg args: the argparse object returned by ``parse_arguments()``.
+
+    """
     _log.debug('token:          %s', args.token)
 
     acls = APP.config['ADMIN_API_ACLS']
@@ -184,7 +208,11 @@ def do_info_admin_token(args):
 
 
 def do_expire_admin_token(args):
-    """ Expire a specific admin token. """
+    """ Expire a specific admin token.
+
+    :arg args: the argparse object returned by ``parse_arguments()``.
+
+    """
     _log.debug('token:          %s', args.token)
 
     acls = APP.config['ADMIN_API_ACLS']
@@ -207,7 +235,11 @@ def do_expire_admin_token(args):
 
 
 def do_create_admin_token(args):
-    """ Create a new admin token. """
+    """ Create a new admin token.
+
+    :arg args: the argparse object returned by ``parse_arguments()``.
+
+    """
     _log.debug('user:          %s', args.user)
     # Validate user first
     pagure.lib.get_user(SESSION, args.user)

From 6bf2587f01baa963fc77be5a7e62ecc4c46b42a0 Mon Sep 17 00:00:00 2001
From: Pierre-Yves Chibon <pingou@pingoured.fr>
Date: Mar 22 2017 08:57:29 +0000
Subject: [PATCH 4/6] Restructure how the argument parsing is done as advised by @bowlofeggs


---

diff --git a/pagure/cli/admin.py b/pagure/cli/admin.py
index 4dc45f4..e9994c4 100644
--- a/pagure/cli/admin.py
+++ b/pagure/cli/admin.py
@@ -29,79 +29,118 @@ from pagure import (SESSION, APP, generate_user_key_files)  # noqa
 _log = logging.getLogger(__name__)
 
 
-def parse_arguments():
-    """ Set-up the argument parsing. """
-    parser = argparse.ArgumentParser(
-        description='The admin CLI for this pagure instance')
-
-    parser.add_argument(
-        '--debug', default=False, action='store_true',
-        help='Increase the verbosity of the information displayed')
-    parser.set_defaults(func=lambda a, k: print(parser.format_help()))
-
-    subparsers = parser.add_subparsers(title='actions')
-
-    # refresh-gitolite
-    parser_gitolite = subparsers.add_parser(
+def _parser_refresh_gitolite(subparser):
+    """ Set up the CLI argument parser for the refresh-gitolite action. """
+    local_parser = subparser.add_parser(
         'refresh-gitolite',
         help='Re-generate the gitolite config file')
-    parser_gitolite.set_defaults(func=do_generate_acl)
+    local_parser.set_defaults(func=do_generate_acl)
 
-    # refresh-ssh
-    parser_ssh = subparsers.add_parser(
+
+def _parser_refresh_ssh(subparser):
+    """ Set up the CLI argument parser for the refresh-ssh action. """
+    local_parser = subparser.add_parser(
         'refresh-ssh',
         help="Re-write to disk every user's ssh key stored in the database")
-    parser_ssh.set_defaults(func=do_refresh_ssh)
+    local_parser.set_defaults(func=do_refresh_ssh)
 
-    # clear-hook-token
-    parser_hook_token = subparsers.add_parser(
+
+def _parser_clear_hook_token(subparser):
+    """ Set up the CLI argument parser for the clear-hook-token action. """
+    local_parser = subparser.add_parser(
         'clear-hook-token',
         help='Generate a new hook token for every project in this instance')
-    parser_hook_token.set_defaults(func=do_generate_hook_token)
-
-    # Admin token actions
-    parser_admin_token = subparsers.add_parser(
-        'admin-token',
-        help='Manages the admin tokens for this instance')
+    local_parser.set_defaults(func=do_generate_hook_token)
 
-    subsubparsers = parser_admin_token.add_subparsers(title='actions')
 
-    # List admin token
-    parser_admin_list_token = subsubparsers.add_parser(
+def _parser_admin_token_list(subparser):
+    """ Set up the CLI argument parser for the admin-token list action. """
+    local_parser = subparser.add_parser(
         'list',  help="List the API admin token")
-    parser_admin_list_token.add_argument(
+    local_parser.add_argument(
         '--user',
         help="User to associate or associated with the token")
-    parser_admin_list_token.add_argument(
+    local_parser.add_argument(
         '--token', help="API token")
-    parser_admin_list_token.add_argument(
+    local_parser.add_argument(
         '--active', default=False, action='store_true',
         help="Only list active API token")
-    parser_admin_list_token.add_argument(
+    local_parser.add_argument(
         '--expired', default=False, action='store_true',
         help="Only list expired API token")
-    parser_admin_token.set_defaults(func=do_list_admin_token)
+    local_parser.set_defaults(func=do_list_admin_token)
+
 
-    # Info about admin token
-    parser_admin_info_token = subsubparsers.add_parser(
+def _parser_admin_token_info(subparser):
+    """ Set up the CLI argument parser for the admin-token info action. """
+    local_parser = subparser.add_parser(
         'info',  help="Provide some information about a specific API token")
-    parser_admin_info_token.add_argument(
+    local_parser.add_argument(
         'token', help="API token")
-    parser_admin_info_token.set_defaults(func=do_info_admin_token)
+    local_parser.set_defaults(func=do_info_admin_token)
+
 
+def _parser_admin_token_expire(subparser):
+    """ Set up the CLI argument parser for the admin-token expire action. """
     # Expire admin token
-    parser_admin_expire_token = subsubparsers.add_parser(
+    local_parser = subparser.add_parser(
         'expire',  help="Expire a specific API token")
-    parser_admin_expire_token.add_argument(
+    local_parser.add_argument(
         'token', help="API token")
-    parser_admin_expire_token.set_defaults(func=do_expire_admin_token)
+    local_parser.set_defaults(func=do_expire_admin_token)
 
+
+def _parser_admin_token_create(subparser):
+    """ Set up the CLI argument parser for the admin-token create action. """
     # Create admin token
-    parser_admin_create_token = subsubparsers.add_parser(
+    local_parser = subparser.add_parser(
         'create',  help="Create a new API token")
-    parser_admin_create_token.add_argument(
+    local_parser.add_argument(
         'user', help="User to associate with the token")
-    parser_admin_create_token.set_defaults(func=do_create_admin_token)
+    local_parser.set_defaults(func=do_create_admin_token)
+
+
+def _parser_admin_token(subparser):
+    """ Set up the CLI argument parser for the admin-token action. """
+    local_parser = subparser.add_parser(
+        'admin-token',
+        help='Manages the admin tokens for this instance')
+
+    subsubparser = local_parser.add_subparsers(title='actions')
+
+    # list
+    _parser_admin_token_list(subsubparser)
+    # info
+    _parser_admin_token_info(subsubparser)
+    # expire
+    _parser_admin_token_expire(subsubparser)
+    # create
+    _parser_admin_token_create(subsubparser)
+
+
+def parse_arguments():
+    """ Set-up the argument parsing. """
+    parser = argparse.ArgumentParser(
+        description='The admin CLI for this pagure instance')
+
+    parser.add_argument(
+        '--debug', default=False, action='store_true',
+        help='Increase the verbosity of the information displayed')
+    parser.set_defaults(func=lambda a, k: print(parser.format_help()))
+
+    subparser = parser.add_subparsers(title='actions')
+
+    # refresh-gitolite
+    _parser_refresh_gitolite(subparser)
+
+    # refresh-ssh
+    _parser_refresh_ssh(subparser)
+
+    # clear-hook-token
+    _parser_clear_hook_token(subparser)
+
+    # Admin token actions
+    _parser_admin_token(subparser)
 
     return parser.parse_args()
 

From 12c3d1fe5f6b441badd0c35214fe9e6deff21632 Mon Sep 17 00:00:00 2001
From: Pierre-Yves Chibon <pingou@pingoured.fr>
Date: Mar 22 2017 08:57:29 +0000
Subject: [PATCH 5/6] Wrap the raw_input into its own method so we can easily patch it for our tests


---

diff --git a/pagure/cli/admin.py b/pagure/cli/admin.py
index e9994c4..2c43e3a 100644
--- a/pagure/cli/admin.py
+++ b/pagure/cli/admin.py
@@ -152,6 +152,11 @@ def _ask_confirmation():
     return action.lower() in ['y', 'yes']
 
 
+def _get_input(text):
+    ''' Ask the user for input. '''
+    return raw_input(text)
+
+
 def do_generate_acl(_):
     """ Regenerate the gitolite ACL file.
 
@@ -288,7 +293,7 @@ def do_create_admin_token(args):
         print('%s.  %s' % (idx, acl))
 
     print('Which ACLs do you want to associated with this token?')
-    acls = raw_input('(Conna separated list): ')
+    acls = _get_input('(Coma separated list): ')
     acls_idx = [int(acl.strip()) for acl in acls.split(',')]
     acls = [acls_list[acl] for acl in acls_idx]
 

From d4c9da2d9e55ff2e9a42fe0463333dd0e2fee0de Mon Sep 17 00:00:00 2001
From: Pierre-Yves Chibon <pingou@pingoured.fr>
Date: Mar 22 2017 08:57:29 +0000
Subject: [PATCH 6/6] Add unit-tests for pagure-admin, especially the part for admin-token


---

diff --git a/tests/test_pagure_admin.py b/tests/test_pagure_admin.py
new file mode 100644
index 0000000..c21ea13
--- /dev/null
+++ b/tests/test_pagure_admin.py
@@ -0,0 +1,368 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2017 - Copyright Red Hat Inc
+
+ Authors:
+   Pierre-Yves Chibon <pingou@pingoured.fr>
+
+"""
+
+__requires__ = ['SQLAlchemy >= 0.8']
+import pkg_resources  # noqa
+
+import unittest  # noqa
+import shutil  # noqa
+import subprocess  # noqa
+import sys  # noqa
+import os  # noqa
+
+import munch  # noqa
+from mock import patch, MagicMock  # noqa
+
+sys.path.insert(0, os.path.join(os.path.dirname(
+    os.path.abspath(__file__)), '..'))
+
+import pagure.cli.admin  # noqa
+import pagure.lib.model  # noqa
+import tests  # noqa
+
+PAGURE_ADMIN = os.path.abspath(
+    os.path.join(tests.HERE, '..', 'pagure', 'cli', 'admin.py'))
+
+
+def _get_ouput(cmd):
+    """ Returns the std-out of the command specified.
+
+    :arg cmd: the command to run provided as a list
+    :type cmd: list
+
+    """
+    my_env = os.environ.copy()
+    my_env["PYTHONPATH"] = os.path.abspath(os.path.join(tests.HERE, '..'))
+    output = subprocess.Popen(
+        cmd,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        env=my_env,
+    ).communicate()
+
+    return output
+
+
+class PagureAdminHelptests(tests.Modeltests):
+    """ Tests for pagure-admin --help """
+
+    def test_parse_arguments_help(self):
+        """ Test the parse_arguments function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py [-h] [--debug]
+                {refresh-gitolite,refresh-ssh,clear-hook-token,admin-token}
+                ...
+
+The admin CLI for this pagure instance
+
+optional arguments:
+  -h, --help            show this help message and exit
+  --debug               Increase the verbosity of the information displayed
+
+actions:
+  {refresh-gitolite,refresh-ssh,clear-hook-token,admin-token}
+    refresh-gitolite    Re-generate the gitolite config file
+    refresh-ssh         Re-write to disk every user's ssh key stored in the
+                        database
+    clear-hook-token    Generate a new hook token for every project in this
+                        instance
+    admin-token         Manages the admin tokens for this instance
+''')
+
+    def test_parser_refresh_gitolite_help(self):
+        """ Test the parser_refresh_gitolite function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'refresh-gitolite', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py refresh-gitolite [-h]
+
+optional arguments:
+  -h, --help  show this help message and exit
+''')
+
+    def test_parser_refresh_ssh_help(self):
+        """ Test the parser_refresh_ssh function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'refresh-ssh', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py refresh-ssh [-h]
+
+optional arguments:
+  -h, --help  show this help message and exit
+''')
+
+    def test_parser_clear_hook_token_help(self):
+        """ Test the parser_clear_hook_token function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'clear-hook-token', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py clear-hook-token [-h]
+
+optional arguments:
+  -h, --help  show this help message and exit
+''')
+
+    def test_parser_admin_token_help(self):
+        """ Test the parser_admin_token function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py admin-token [-h] {list,info,expire,create} ...
+
+optional arguments:
+  -h, --help            show this help message and exit
+
+actions:
+  {list,info,expire,create}
+    list                List the API admin token
+    info                Provide some information about a specific API token
+    expire              Expire a specific API token
+    create              Create a new API token
+''')
+
+    def test_parser_admin_token_create_help(self):
+        """ Test the parser_admin_token_create function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'create', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py admin-token create [-h] user
+
+positional arguments:
+  user        User to associate with the token
+
+optional arguments:
+  -h, --help  show this help message and exit
+''')
+
+    def test_parser_admin_token_list_help(self):
+        """ Test the _parser_admin_token_list function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'list', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py admin-token list [-h] [--user USER] [--token TOKEN] [--active]
+                                 [--expired]
+
+optional arguments:
+  -h, --help     show this help message and exit
+  --user USER    User to associate or associated with the token
+  --token TOKEN  API token
+  --active       Only list active API token
+  --expired      Only list expired API token
+''')  # noqa
+
+    def test_parser_admin_token_info_help(self):
+        """ Test the _parser_admin_token_info function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'info', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py admin-token info [-h] token
+
+positional arguments:
+  token       API token
+
+optional arguments:
+  -h, --help  show this help message and exit
+''')
+
+    def test_parser_admin_token_expire_help(self):
+        """ Test the _parser_admin_token_expire function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'expire', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[0],
+            '''usage: admin.py admin-token expire [-h] token
+
+positional arguments:
+  token       API token
+
+optional arguments:
+  -h, --help  show this help message and exit
+''')
+
+    def test_parser_admin_token_invalid_help(self):
+        """ Test the _parser_admin_token_expire function of pagure-admin. """
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'foo', '--help']
+        self.assertEqual(
+            _get_ouput(cmd)[1],
+            '''usage: admin.py admin-token [-h] {list,info,expire,create} ...
+admin.py admin-token: error: invalid choice: 'foo' (choose from 'list', 'info', 'expire', 'create')
+''')  # noqa
+
+
+class PagureAdminAdminTokenEmptytests(tests.Modeltests):
+    """ Tests for pagure-admin admin-token when there is nothing in the DB
+    """
+    def setUp(self):
+        """ Set up the environnment, ran before every tests. """
+        super(PagureAdminAdminTokenEmptytests, self).setUp()
+
+        self.configfile = os.path.join(self.path, 'config')
+        self.dbpath = "sqlite:///%s/pagure_dev.sqlite" % self.path
+        with open(self.configfile, 'w') as stream:
+            stream.write('DB_URL="%s"\n' % self.dbpath)
+
+        os.environ['PAGURE_CONFIG'] = self.configfile
+
+        createdb = os.path.abspath(
+            os.path.join(tests.HERE, '..', 'createdb.py'))
+        cmd = ['python', createdb]
+        _get_ouput(cmd)
+
+    def tearDown(self):
+        """ Tear down the environnment after running the tests. """
+        super(PagureAdminAdminTokenEmptytests, self).tearDown()
+        del(os.environ['PAGURE_CONFIG'])
+
+    def test_do_create_admin_token_no_user(self):
+        """ Test the do_create_admin_token function of pagure-admin without
+        user.
+        """
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'create', 'pingou']
+        self.assertEqual(_get_ouput(cmd)[0], 'No user "pingou" found\n')
+
+    def test_do_list_admin_token_empty(self):
+        """ Test the do_list_admin_token function of pagure-admin when there
+        are not tokens in the db.
+        """
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'list']
+        self.assertEqual(_get_ouput(cmd)[0], 'No admin tokens found\n')
+
+
+class PagureAdminAdminTokentests(tests.Modeltests):
+    """ Tests for pagure-admin admin-token """
+
+    def setUp(self):
+        """ Set up the environnment, ran before every tests. """
+        super(PagureAdminAdminTokentests, self).setUp()
+
+        self.configfile = os.path.join(self.path, 'config')
+        self.dbpath = "sqlite:///%s/pagure_dev.sqlite" % self.path
+        with open(self.configfile, 'w') as stream:
+            stream.write('DB_URL="%s"\n' % self.dbpath)
+
+        os.environ['PAGURE_CONFIG'] = self.configfile
+
+        createdb = os.path.abspath(
+            os.path.join(tests.HERE, '..', 'createdb.py'))
+        cmd = ['python', createdb]
+        _get_ouput(cmd)
+
+        self.session = pagure.lib.model.create_tables(
+            self.dbpath, acls=pagure.APP.config.get('ACLS', {}))
+
+        # Create the user pingou
+        item = pagure.lib.model.User(
+            user='pingou',
+            fullname='PY C',
+            password='foo',
+            default_email='bar@pingou.com',
+        )
+        self.session.add(item)
+        item = pagure.lib.model.UserEmail(
+            user_id=1,
+            email='bar@pingou.com')
+        self.session.add(item)
+        self.session.commit()
+
+        # Make the imported pagure use the correct db session
+        pagure.cli.admin.SESSION = self.session
+
+    def tearDown(self):
+        """ Tear down the environnment after running the tests. """
+        super(PagureAdminAdminTokentests, self).tearDown()
+        del(os.environ['PAGURE_CONFIG'])
+
+    @patch('pagure.cli.admin._get_input')
+    @patch('pagure.cli.admin._ask_confirmation')
+    def test_do_create_admin_token(self, conf, rinp):
+        """ Test the do_create_admin_token function of pagure-admin. """
+        conf.return_value = True
+        rinp.return_value = '1,2,3'
+
+        args = munch.Munch({'user': 'pingou'})
+        pagure.cli.admin.do_create_admin_token(args)
+
+        # Check the outcome
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'list']
+        output = _get_ouput(cmd)[0]
+        self.assertNotEqual(output, 'No user "pingou" found\n')
+        self.assertEqual(len(output.split('\n')), 2)
+        self.assertIn(' -- pingou -- ', output)
+
+    @patch('pagure.cli.admin._get_input')
+    @patch('pagure.cli.admin._ask_confirmation')
+    def test_do_info_admin_token(self, conf, rinp):
+        """ Test the do_info_admin_token function of pagure-admin. """
+        # Create an admin token to use
+        conf.return_value = True
+        rinp.return_value = '1,2,3'
+
+        args = munch.Munch({'user': 'pingou'})
+        pagure.cli.admin.do_create_admin_token(args)
+
+        # Retrieve the token
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'list']
+        output = _get_ouput(cmd)[0]
+        self.assertNotEqual(output, 'No user "pingou" found\n')
+        self.assertEqual(len(output.split('\n')), 2)
+        self.assertIn(' -- pingou -- ', output)
+
+        token = output.split(' ', 1)[0]
+
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'info', token]
+        output = _get_ouput(cmd)[0]
+        self.assertIn(' -- pingou -- ', output.split('\n', 1)[0])
+        self.assertEqual(
+            output.split('\n', 1)[1], '''ACLs:
+  - issue_create
+  - pull_request_comment
+  - pull_request_flag
+''')
+
+    @patch('pagure.cli.admin._get_input')
+    @patch('pagure.cli.admin._ask_confirmation')
+    def test_do_expire_admin_token(self, conf, rinp):
+        """ Test the do_expire_admin_token function of pagure-admin. """
+        # Create an admin token to use
+        conf.return_value = True
+        rinp.return_value = '1,2,3'
+
+        args = munch.Munch({'user': 'pingou'})
+        pagure.cli.admin.do_create_admin_token(args)
+
+        # Retrieve the token
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'list']
+        output = _get_ouput(cmd)[0]
+        self.assertNotEqual(output, 'No user "pingou" found\n')
+        self.assertEqual(len(output.split('\n')), 2)
+        self.assertIn(' -- pingou -- ', output)
+
+        token = output.split(' ', 1)[0]
+
+        # Before
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'list', '--active']
+        output = _get_ouput(cmd)[0]
+        self.assertNotEqual(output, 'No admin tokens found\n')
+        self.assertEqual(len(output.split('\n')), 2)
+        self.assertIn(' -- pingou -- ', output)
+
+        # Expire the token
+        args = munch.Munch({'token': token})
+        pagure.cli.admin.do_expire_admin_token(args)
+
+        # After
+        cmd = ['python', PAGURE_ADMIN, 'admin-token', 'list', '--active']
+        output = _get_ouput(cmd)[0]
+        self.assertEqual(output, 'No admin tokens found\n')
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)

