diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py
index 49407c6..448d570 100644
--- a/pagure/lib/__init__.py
+++ b/pagure/lib/__init__.py
@@ -3231,7 +3231,7 @@ def get_api_token(session, token_str):
return query.first()
-def get_acls(session):
+def get_acls(session, restrict=None):
""" Returns all the possible ACLs a token can have according to the
database.
"""
@@ -3240,6 +3240,15 @@ def get_acls(session):
).order_by(
model.ACL.name
)
+ if restrict:
+ if isinstance(restrict, list):
+ query = query.filter(
+ model.ACL.name.in_(restrict)
+ )
+ else:
+ query = query.filter(
+ model.ACL.name == restrict
+ )
return query.all()
@@ -3259,7 +3268,7 @@ def add_token_to_user(session, project, acls, username):
token = pagure.lib.model.Token(
id=pagure.lib.login.id_generator(64),
user_id=user.id,
- project_id=project.id,
+ project_id=project.id if project else None,
expiration=datetime.datetime.utcnow() + datetime.timedelta(days=60)
)
session.add(token)
diff --git a/pagure/templates/add_token.html b/pagure/templates/add_token.html
index 11e9b31..b6aed5c 100644
--- a/pagure/templates/add_token.html
+++ b/pagure/templates/add_token.html
@@ -1,11 +1,15 @@
+{% if repo %}
{% extends "repo_master.html" %}
+{% else %}
+{% extends "master.html" %}
+{% endif %}
{% from "_formhelper.html" import render_field_in_row %}
{% set tag = "home" %}
{% block title %}Create token{% endblock %}
-
-{% block repo %}
+{% macro render_page() %}
+
@@ -21,10 +25,14 @@
After that, click 'Create' to generate a token with the selected
permissions.
+ {% if repo %}
+
+{% endmacro %}
+
+
+{% block content %}
+ {{ render_page() }}
+{% endblock %}
+
+{% block repo %}
+ {{ render_page() }}
{% endblock %}
diff --git a/pagure/templates/settings.html b/pagure/templates/settings.html
index 2ff690f..2a90324 100644
--- a/pagure/templates/settings.html
+++ b/pagure/templates/settings.html
@@ -205,7 +205,6 @@
-
diff --git a/pagure/templates/user_settings.html b/pagure/templates/user_settings.html
index fe22f90..8cdd62c 100644
--- a/pagure/templates/user_settings.html
+++ b/pagure/templates/user_settings.html
@@ -148,12 +148,104 @@
-
- {% if config.get('PAGURE_AUTH')=='local' %}
-
Change password
+ {% if config.get('PAGURE_AUTH')=='local' %}
+
+ {% endif %}
+
+
+
+
+
+
+
+ API keys are tokens used to authenticate you on pagure. They can also
+ be used to grant access to 3rd party application to behave on all
+ projects in your name.
+
+
+ These are your personal tokens; they are not visible to others.
+
+
+ These keys are valid for 60 days.
+
+
+ These keys are private, make sure to store in a safe place and
+ do not share it.
+
+
+ {% if user.tokens %}
+
+ {% for token in user.tokens %}
+ {% if not token.project %}
+
+
+ {% if token.expired %}
+ Expired since {{ token.expiration.date() }}
+ {% else %}
+ Valid until: {{ token.expiration.date() }}
+
+ {% endif %}
+
+ ACLs
+
+
+
+
+
+
+
+ {% for acl in token.acls_list_pretty %}
+ {{ acl }}
+ {% endfor %}
+
+
+
+
+
+
{% endif %}
+ {% endfor %}
+
+ {% endif %}
+
+
{% endblock %}
diff --git a/pagure/ui/app.py b/pagure/ui/app.py
index 693ab36..82759de 100644
--- a/pagure/ui/app.py
+++ b/pagure/ui/app.py
@@ -1,16 +1,17 @@
# -*- coding: utf-8 -*-
"""
- (c) 2014-2015 - Copyright Red Hat Inc
+ (c) 2014-2017 - Copyright Red Hat Inc
Authors:
Pierre-Yves Chibon
"""
-import flask
+import datetime
from math import ceil
+import flask
from sqlalchemy.exc import SQLAlchemyError
import pagure.exceptions
@@ -756,3 +757,92 @@ def ssh_hostkey():
return flask.render_template(
'doc_ssh_keys.html',
)
+
+
+@APP.route('/settings/token/new/', methods=('GET', 'POST'))
+@APP.route('/settings/token/new', methods=('GET', 'POST'))
+@login_required
+def add_user_token():
+ """ Create an user token (not project specific).
+ """
+ if admin_session_timedout():
+ if flask.request.method == 'POST':
+ flask.flash('Action canceled, try it again', 'error')
+ return flask.redirect(
+ flask.url_for('auth_login', next=flask.request.url))
+
+ # Ensure the user is in the DB at least
+ user = pagure.lib.search_user(
+ SESSION, username=flask.g.fas_user.username)
+ if not user:
+ flask.abort(404, 'User not found')
+
+ acls = pagure.lib.get_acls(
+ SESSION, restrict=APP.config.get('CROSS_PROJECT_ACLS'))
+ form = pagure.forms.NewTokenForm(acls=acls)
+
+ if form.validate_on_submit():
+ try:
+ msg = pagure.lib.add_token_to_user(
+ SESSION,
+ project=None,
+ acls=form.acls.data,
+ username=flask.g.fas_user.username,
+ )
+ SESSION.commit()
+ flask.flash(msg)
+ return flask.redirect(flask.url_for('.user_settings'))
+ except SQLAlchemyError as err: # pragma: no cover
+ SESSION.rollback()
+ APP.logger.exception(err)
+ flask.flash('API key could not be added', 'error')
+
+ # When form is displayed after an empty submission, show an error.
+ if form.errors.get('acls'):
+ flask.flash('You must select at least one permission.', 'error')
+
+ return flask.render_template(
+ 'add_token.html',
+ select='settings',
+ form=form,
+ acls=acls,
+ )
+
+
+@APP.route('/settings/token/revoke//', methods=['POST'])
+@APP.route('/settings/token/revoke/', methods=['POST'])
+@login_required
+def revoke_api_user_token(token_id):
+ """ Revokie an user token (ie: not project specific).
+ """
+ if admin_session_timedout():
+ flask.flash('Action canceled, try it again', 'error')
+ url = flask.url_for(
+ 'view_settings', username=username, repo=repo,
+ namespace=namespace)
+ return flask.redirect(
+ flask.url_for('auth_login', next=url))
+
+ token = pagure.lib.get_api_token(SESSION, token_id)
+
+ if not token \
+ or token.user.username != flask.g.fas_user.username:
+ flask.abort(404, 'Token not found')
+
+ form = pagure.forms.ConfirmationForm()
+
+ if form.validate_on_submit():
+ try:
+ if token.expiration >= datetime.datetime.utcnow():
+ token.expiration = datetime.datetime.utcnow()
+ SESSION.add(token)
+ SESSION.commit()
+ flask.flash('Token revoked')
+ except SQLAlchemyError as err: # pragma: no cover
+ SESSION.rollback()
+ APP.logger.exception(err)
+ flask.flash(
+ 'Token could not be revoked, please contact an admin',
+ 'error')
+
+ return flask.redirect(flask.url_for('.user_settings'))
diff --git a/tests/test_pagure_flask_ui_app.py b/tests/test_pagure_flask_ui_app.py
index 3f2ecf2..a9acd72 100644
--- a/tests/test_pagure_flask_ui_app.py
+++ b/tests/test_pagure_flask_ui_app.py
@@ -11,6 +11,7 @@
__requires__ = ['SQLAlchemy >= 0.8']
import pkg_resources
+import datetime
import unittest
import shutil
import sys
@@ -1207,6 +1208,174 @@ class PagureFlaskApptests(tests.Modeltests):
self.assertEqual(output.status_code, 404)
pagure.APP.config['ENABLE_TICKETS'] = True
+ @patch('pagure.ui.app.admin_session_timedout')
+ def test_add_user_token(self, ast):
+ """ Test the add_user_token endpoint. """
+ ast.return_value = False
+ self.test_new_project()
+
+ user = tests.FakeUser()
+ with tests.user_set(pagure.APP, user):
+ output = self.app.get('/settings/token/new/')
+ self.assertEqual(output.status_code, 404)
+ self.assertTrue('Page not found (404) ' in output.data)
+
+ user.username = 'foo'
+ with tests.user_set(pagure.APP, user):
+ output = self.app.get('/settings/token/new')
+ self.assertEqual(output.status_code, 200)
+ self.assertIn(
+ '