From 19091b7f11ef5459317742e944f2b226cddd85f9 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Feb 09 2017 12:49:12 +0000 Subject: [PATCH 1/6] Fix autogenerating alembic Signed-off-by: Patrick Uiterwijk --- diff --git a/alembic/env.py b/alembic/env.py index 1c11bd2..fa22c7f 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -33,7 +33,7 @@ except ImportError: # add your model's MetaData object here # for 'autogenerate' support -target_metadata = pagure.lib.model.BASE +target_metadata = pagure.lib.model.BASE.metadata # other values from the config, defined by the needs of env.py, # can be acquired: From 72ffe71f4163ae674e388a418140166ab7888992 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Feb 09 2017 13:13:43 +0000 Subject: [PATCH 2/6] Make is_valid_ssh_key return key summary Signed-off-by: Patrick Uiterwijk --- diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 4cd4610..de97d34 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -202,8 +202,10 @@ def is_valid_ssh_key(key): stdin=f, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - proc.communicate() - return proc.returncode == 0 + stdout, stderr = proc.communicate() + if proc.returncode != 0: + return False + return stdout def are_valid_ssh_keys(keys): From 9a7915881e348def743631a6a1702fb52438e1e3 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Feb 09 2017 13:24:52 +0000 Subject: [PATCH 3/6] Add Deploy Keys Signed-off-by: Patrick Uiterwijk --- diff --git a/pagure/forms.py b/pagure/forms.py index cdf9d3a..a9cc117 100644 --- a/pagure/forms.py +++ b/pagure/forms.py @@ -419,6 +419,20 @@ class UserSettingsForm(PagureForm): ) +class AddDeployKeyForm(PagureForm): + ''' Form to add a deploy key to a project. ''' + ssh_key = wtforms.TextField( + 'SSH Key *', + [wtforms.validators.Required()] + # TODO: Add an ssh key validator? + ) + pushaccess = wtforms.BooleanField( + 'Push access', + [wtforms.validators.optional()], + false_values=('false', '', False, 'False', 0, '0'), + ) + + class AddUserForm(PagureForm): ''' Form to add a user to a project. ''' user = wtforms.TextField( diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index de97d34..af9b15a 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -28,6 +28,7 @@ import tempfile import subprocess import urlparse import uuid +import werkzeug import bleach import redis @@ -213,6 +214,51 @@ def are_valid_ssh_keys(keys): for key in keys.split('\n')]) +def create_deploykeys_ssh_keys_on_disk(project, gitolite_keydir): + ''' Create the ssh keys for the projects' deploy keys on the key dir. + + This method does NOT support multiple ssh keys per deploy key. + ''' + if not gitolite_keydir: + # Nothing to do here, move right along + return + + #keyline_file = os.path.join(gitolite_keydir, + # 'keys_%i' % i, + # '%s.pub' % user.user) + # First remove deploykeys that no longer exist + keyfiles = ['deploykey_%s_%s.pub' % + (werkzeug.secure_filename(project.fullname), + key.id) + for key in project.deploykeys] + + project_key_dir = os.path.join(gitolite_keydir, 'deploykeys', + project.fullname) + if not os.path.exists(project_key_dir): + os.mkdir(project_key_dir) + + for keyfile in os.listdir(project_key_dir): + if keyfile not in keyfiles: + # This key is no longer in the project. Remove it. + os.remove(os.path.join(project_key_dir, keyfile)) + + for deploykey in project.deploykeys: + # See the comment in lib/git.py:write_gitolite_acls about why this + # name for a file is sane and does not inject a new security risk. + keyfile = 'deploykey_%s_%s' % ( + werkzeug.secure_filename(project.fullname), + deploykey.id) + if not os.path.exists(os.path.join(project_key_dir, keyfile)): + # We only take the very first key - deploykeys must be single keys + key = deploykey.public_ssh_key.split('\n')[0] + if not key: + continue + if not is_valid_ssh_key(key): + continue + with open(os.path.join(project_key_dir, keyfile), 'w') as f: + f.write(deploykey.public_ssh_key) + + def create_user_ssh_keys_on_disk(user, gitolite_keydir): ''' Create the ssh keys for the user on the specific folder. @@ -826,6 +872,52 @@ def edit_issue_tags( return msgs +def add_deploykey_to_project(session, project, ssh_key, pushaccess, user): + ''' Add a deploy key to a specified project. ''' + ssh_key = ssh_key.strip() + + if '\n' in ssh_key: + raise pagure.exceptions.PagureException( + 'Deploy key can only be single keys.' + ) + + ssh_short_key = is_valid_ssh_key(ssh_key) + if ssh_short_key in [None, False]: + raise pagure.exceptions.PagureException( + 'Deploy key invalid.' + ) + + # We are sure that this only contains a single key, but ssh-keygen still + # return a \n at the end + ssh_short_key = ssh_short_key.split('\n')[0] + + # Make sure that this key is not a deploy key in this or another project. + # If we dupe keys, gitolite might choke. + ssh_search_key = ssh_short_key.split(' ')[1] + if session.query(model.DeployKey).filter( + model.DeployKey.ssh_search_key==ssh_search_key).count() != 0: + raise pagure.exceptions.PagureException( + 'Deploy key already exists.' + ) + + user_obj = get_user(session, user) + new_key_obj = model.DeployKey( + project_id=project.id, + pushaccess=pushaccess, + public_ssh_key=ssh_key, + ssh_short_key=ssh_short_key, + ssh_search_key=ssh_search_key, + creator_user_id=user_obj.id) + + session.add(new_key_obj) + # Make sure we won't have SQLAlchemy error before we continue + session.flush() + + # We do not send any notifications on purpose + + return 'Deploy key added' + + def add_user_to_project(session, project, new_user, user): ''' Add a specified user to a specified project. ''' new_user_obj = get_user(session, new_user) diff --git a/pagure/lib/git.py b/pagure/lib/git.py index 46221e1..b722988 100644 --- a/pagure/lib/git.py +++ b/pagure/lib/git.py @@ -110,6 +110,22 @@ def write_gitolite_acls(session, configfile): for user in project.users: if user != project.user: config.append(' RW+ = %s' % user.user) + for deploykey in project.deploykeys: + access = 'R' + if deploykey.pushaccess: + access = 'RW+' + # Note: the replace of / with _ is because gitolite users can't + # contain a /. At first, this might look like deploy keys in a + # project called $namespace_$project would give access to the + # repos of a project $namespace/$project or vica versa, however + # this is NOT the case because we add the deploykey.id to the + # end of the deploykey name, which means it is unique. The + # project name is solely there to make it easier to determine + # what project created the deploykey for admins. + config.append(' %s = deploykey_%s_%s' % + (access, + werkzeug.secure_filename(project.fullname), + deploykey.id)) config.append('') with open(configfile, 'w') as stream: diff --git a/pagure/lib/model.py b/pagure/lib/model.py index d0bb402..51d9b5e 100644 --- a/pagure/lib/model.py +++ b/pagure/lib/model.py @@ -635,6 +635,44 @@ class ProjectUser(BASE): index=True) +class DeployKey(BASE): + """ Stores information about deployment keys. + + Table -- deploykeys + """ + + __tablename__ = 'deploykeys' + id = sa.Column(sa.Integer, primary_key=True) + project_id = sa.Column( + sa.Integer, + sa.ForeignKey( + 'projects.id', onupdate='CASCADE', ondelete='CASCADE', + )) + pushaccess = sa.Column(sa.Boolean, nullable=False, default=False) + public_ssh_key = sa.Column(sa.Text, nullable=False) + ssh_short_key = sa.Column(sa.Text, nullable=False) + ssh_search_key = sa.Column(sa.Text, nullable=False) + creator_user_id = sa.Column( + sa.Integer, + sa.ForeignKey( + 'users.id', onupdate='CASCADE', + ), + nullable=False, + index=True) + date_created = sa.Column(sa.DateTime, nullable=False, + default=datetime.datetime.utcnow) + + # Relations + project = relation( + 'Project', foreign_keys=[project_id], remote_side=[Project.id], + backref=backref( + 'deploykeys', cascade="delete, delete-orphan", single_parent=True) + ) + + creator_user = relation('User', foreign_keys=[creator_user_id], + remote_side=[User.id]) + + class Issue(BASE): """ Stores the issues reported on a project. diff --git a/pagure/templates/add_deploykey.html b/pagure/templates/add_deploykey.html new file mode 100644 index 0000000..6ff6dde --- /dev/null +++ b/pagure/templates/add_deploykey.html @@ -0,0 +1,42 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field_in_row %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% set tag = "home" %} + +{% block header %} + +{% endblock %} + +{% block title %}Add deploy key - {{ + repo.namespace + '/' if repo.namespace }}{{ repo.name }}{% endblock %} + +{% block repo %} +
+
+
+ Add deploy key to the {{repo.name}} project +
+
+
+ +
+ + +
+ {{ render_bootstrap_field(form.pushaccess, field_description="Do you want to give this key push access?") }} + +

+ + + {{ form.csrf_token }} +

+
+
+
+
+ +{% endblock %} diff --git a/pagure/templates/settings.html b/pagure/templates/settings.html index c7dc777..1836852 100644 --- a/pagure/templates/settings.html +++ b/pagure/templates/settings.html @@ -462,6 +462,54 @@ {% endif %} +
+
+
+ Deploy Keys +
+
+ +

Below are this projects' deploy keys.

+ +

+ + add deploy key + +

+
+
    + {% for deploykey in repo.deploykeys %} +
  • + {{ deploykey.ssh_short_key }} + {% if deploykey.pushaccess %} + (PUSH ACCESS) + {% endif %} +
    + + {{ form.csrf_token }} +
    +
  • + {% endfor %} +
+
+
+ + {% if plugins %}
diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py index 7d7ef0f..6713176 100644 --- a/pagure/ui/repo.py +++ b/pagure/ui/repo.py @@ -1518,6 +1518,66 @@ def new_repo_hook_token(repo, username=None, namespace=None): namespace=namespace)) +@APP.route('//dropdeploykey/', methods=['POST']) +@APP.route('///dropdeploykey/', methods=['POST']) +@APP.route('/fork///dropdeploykey/', + methods=['POST']) +@APP.route('/fork////dropdeploykey/', + methods=['POST']) +@login_required +def remove_deploykey(repo, keyid, username=None, namespace=None): + """ Remove the specified deploy key from the project. + """ + + 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)) + + repo = flask.g.repo + + if not flask.g.repo_admin: + flask.abort( + 403, + 'You are not allowed to change the deploy keys for this project') + + form = pagure.forms.ConfirmationForm() + if form.validate_on_submit(): + keyids = [str(key.id) for key in repo.deploykeys] + + if str(keyid) not in keyids: + flask.flash( + 'Deploy key does not exist in project.', 'error') + return flask.redirect(flask.url_for( + '.view_settings', repo=repo.name, username=username, + namespace=repo.namespace,) + ) + + for key in repo.deploykeys: + if str(key.id) == str(keyid): + SESSION.delete(key) + break + try: + SESSION.commit() + pagure.lib.git.generate_gitolite_acls() + pagure.lib.create_deploykeys_ssh_keys_on_disk( + repo, + APP.config.get('GITOLITE_KEYDIR', None) + ) + flask.flash('Deploy key removed') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + APP.logger.exception(err) + flask.flash('Deploy key could not be removed', 'error') + + return flask.redirect(flask.url_for( + '.view_settings', repo=repo.name, username=username, + namespace=namespace)) + + @APP.route('//dropuser/', methods=['POST']) @APP.route('///dropuser/', methods=['POST']) @APP.route('/fork///dropuser/', @@ -1577,6 +1637,72 @@ def remove_user(repo, userid, username=None, namespace=None): namespace=namespace)) +@APP.route('//adddeploykey/', methods=('GET', 'POST')) +@APP.route('//adddeploykey', methods=('GET', 'POST')) +@APP.route('///adddeploykey/', methods=('GET', 'POST')) +@APP.route('///adddeploykey', methods=('GET', 'POST')) +@APP.route('/fork///adddeploykey/', methods=('GET', 'POST')) +@APP.route('/fork///adddeploykey', methods=('GET', 'POST')) +@APP.route( + '/fork////adddeploykey/', + methods=('GET', 'POST')) +@APP.route( + '/fork////adddeploykey', + methods=('GET', 'POST')) +@login_required +def add_deploykey(repo, username=None, namespace=None): + """ Add the specified deploy key to the project. + """ + + 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)) + + repo = flask.g.repo + + if not flask.g.repo_admin: + flask.abort( + 403, + 'You are not allowed to add deploy keys to this project') + + form = pagure.forms.AddDeployKeyForm() + + if form.validate_on_submit(): + try: + msg = pagure.lib.add_deploykey_to_project( + SESSION, repo, + ssh_key=form.ssh_key.data, + pushaccess=form.pushaccess.data, + user=flask.g.fas_user.username, + ) + SESSION.commit() + pagure.lib.git.generate_gitolite_acls() + pagure.lib.create_deploykeys_ssh_keys_on_disk( + repo, + APP.config.get('GITOLITE_KEYDIR', None) + ) + flask.flash(msg) + return flask.redirect(flask.url_for( + '.view_settings', repo=repo.name, username=username, + namespace=namespace)) + except pagure.exceptions.PagureException as msg: + SESSION.rollback() + flask.flash(msg, 'error') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + APP.logger.exception(err) + flask.flash('Deploy key could not be added', 'error') + + return flask.render_template( + 'add_deploykey.html', + form=form, + username=username, + repo=repo, + ) + + @APP.route('//adduser/', methods=('GET', 'POST')) @APP.route('//adduser', methods=('GET', 'POST')) @APP.route('///adduser/', methods=('GET', 'POST')) From faa79d2ec2237104125aa4ab2acd2c82856bf3ec Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Feb 09 2017 13:24:53 +0000 Subject: [PATCH 4/6] Add tests for deploy keys Signed-off-by: Patrick Uiterwijk --- diff --git a/tests/test_pagure_flask_ui_repo.py b/tests/test_pagure_flask_ui_repo.py index b52c9a8..68f0902 100644 --- a/tests/test_pagure_flask_ui_repo.py +++ b/tests/test_pagure_flask_ui_repo.py @@ -110,6 +110,109 @@ class PagureFlaskRepotests(tests.Modeltests): @patch('pagure.ui.repo.admin_session_timedout') + def test_add_deploykey(self, ast): + """ Test the add_deploykey endpoint. """ + ast.return_value = False + + # No git repo + output = self.app.get('/foo/adddeploykey') + self.assertEqual(output.status_code, 404) + + tests.create_projects(self.session) + tests.create_projects_git(self.path) + + # User not logged in + output = self.app.get('/test/adddeploykey') + self.assertEqual(output.status_code, 302) + + user = tests.FakeUser() + with tests.user_set(pagure.APP, user): + output = self.app.get('/test/adddeploykey') + self.assertEqual(output.status_code, 403) + + ast.return_value = True + output = self.app.get('/test/adddeploykey') + self.assertEqual(output.status_code, 302) + + # Redirect also happens for POST request + output = self.app.post('/test/adddeploykey') + self.assertEqual(output.status_code, 302) + + # Need to do this un-authentified since our fake user isn't in the DB + # Check the message flashed during the redirect + output = self.app.get('/') + self.assertEqual(output.status_code, 200) + self.assertIn( + '\n Action canceled, try it ' + 'again',output.data) + + ast.return_value = False + + user.username = 'pingou' + with tests.user_set(pagure.APP, user): + output = self.app.get('/test/adddeploykey') + self.assertEqual(output.status_code, 200) + self.assertIn('Add deploy key to the', output.data) + + csrf_token = output.data.split( + 'name="csrf_token" type="hidden" value="')[1].split('">')[0] + + data = { + 'ssh_key': 'asdf', + 'pushaccess': 'false' + } + + # No CSRF token + output = self.app.post('/test/adddeploykey', data=data) + self.assertEqual(output.status_code, 200) + self.assertTrue('Add deploy key to the' in output.data) + + data['csrf_token'] = csrf_token + + # First, invalid SSH key + output = self.app.post('/test/adddeploykey', data=data) + self.assertEqual(output.status_code, 200) + self.assertIn('Add deploy key to the', output.data) + self.assertIn('Deploy key invalid', output.data) + + # Next up, multiple SSH keys + data['ssh_key'] = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAzBMSIlvPRaEiLOTVInErkRIw9CzQQcnslDekAn1jFnGf+SNa1acvbTiATbCX71AA03giKrPxPH79dxcC7aDXerc6zRcKjJs6MAL9PrCjnbyxCKXRNNZU5U9X/DLaaL1b3caB+WD6OoorhS3LTEtKPX8xyjOzhf3OQSzNjhJp5Q==\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAzBMSIlvPRaEiLOTVInErkRIw9CzQQcnslDekAn1jFnGf+SNa1acvbTiATbCX71AA03giKrPxPH79dxcC7aDXerc6zRcKjJs6MAL9PrCjnbyxCKXRNNZU5U9X/DLaaL1b3caB+WD6OoorhS3LTEtKPX8xyjOzhf3OQSzNjhJp5Q==' + output = self.app.post( + '/test/adddeploykey', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn('Deploy key can only be single keys.', output.data) + + # Now, a valid SSH key + data['ssh_key'] = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAzBMSIlvPRaEiLOTVInErkRIw9CzQQcnslDekAn1jFnGf+SNa1acvbTiATbCX71AA03giKrPxPH79dxcC7aDXerc6zRcKjJs6MAL9PrCjnbyxCKXRNNZU5U9X/DLaaL1b3caB+WD6OoorhS3LTEtKPX8xyjOzhf3OQSzNjhJp5Q==' + output = self.app.post( + '/test/adddeploykey', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Settings - test - Pagure', output.data) + self.assertIn('

Settings for test

', output.data) + self.assertIn('Deploy key added', output.data) + self.assertNotIn('PUSH ACCESS', output.data) + + # And now, adding the same key + output = self.app.post( + '/test/adddeploykey', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn('Deploy key already exists', output.data) + + # And next, a key with push access + data['ssh_key'] = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9Xwc2RDzPBhlEDARfHldGjudIVoa04tqT1JVKGQmyllTFz7Rb8CngQL3e7zyNzotnhwYKHdoiLlPkVEiDee4dWMUe48ilqId+FJZQGhyv8fu4BoFdE1AJUVylzmltbLg14VqG5gjTpXgtlrEva9arKwBMHJjRYc8ScaSn3OgyQw==' + data['pushaccess'] = 'true' + output = self.app.post( + '/test/adddeploykey', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Settings - test - Pagure', output.data) + self.assertIn('

Settings for test

', output.data) + self.assertIn('Deploy key added', output.data) + self.assertIn('PUSH ACCESS', output.data) + + + @patch('pagure.ui.repo.admin_session_timedout') def test_add_user(self, ast): """ Test the add_user endpoint. """ ast.return_value = False @@ -386,6 +489,82 @@ class PagureFlaskRepotests(tests.Modeltests): @patch('pagure.ui.repo.admin_session_timedout') + def test_remove_deploykey(self, ast): + """ Test the remove_deploykey endpoint. """ + ast.return_value = False + + # Git repo not found + output = self.app.post('/foo/dropdeploykey/1') + self.assertEqual(output.status_code, 404) + + user = tests.FakeUser() + with tests.user_set(pagure.APP, user): + output = self.app.post('/foo/dropdeploykey/1') + self.assertEqual(output.status_code, 404) + + tests.create_projects(self.session) + tests.create_projects_git(self.path) + + output = self.app.post('/test/dropdeploykey/1') + self.assertEqual(output.status_code, 403) + + ast.return_value = True + output = self.app.post('/test/dropdeploykey/1') + self.assertEqual(output.status_code, 302) + ast.return_value = False + + # User not logged in + output = self.app.post('/test/dropdeploykey/1') + self.assertEqual(output.status_code, 302) + + user.username = 'pingou' + with tests.user_set(pagure.APP, user): + output = self.app.post('/test/settings') + + csrf_token = output.data.split( + 'name="csrf_token" type="hidden" value="')[1].split('">')[0] + + data = {'csrf_token': csrf_token} + + output = self.app.post( + '/test/dropdeploykey/1', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Settings - test - Pagure', output.data) + self.assertIn('

Settings for test

', output.data) + self.assertIn('Deploy key does not exist in project', output.data) + + # Add a deploy key to a project + repo = pagure.lib.get_project(self.session, 'test') + msg = pagure.lib.add_deploykey_to_project( + session=self.session, + project=repo, + ssh_key='ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAzBMSIlvPRaEiLOTVInErkRIw9CzQQcnslDekAn1jFnGf+SNa1acvbTiATbCX71AA03giKrPxPH79dxcC7aDXerc6zRcKjJs6MAL9PrCjnbyxCKXRNNZU5U9X/DLaaL1b3caB+WD6OoorhS3LTEtKPX8xyjOzhf3OQSzNjhJp5Q==', + pushaccess=True, + user='pingou', + ) + self.session.commit() + self.assertEqual(msg, 'Deploy key added') + + with tests.user_set(pagure.APP, user): + output = self.app.post('/test/dropdeploykey/1', follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Settings - test - Pagure', output.data) + self.assertIn('

Settings for test

', output.data) + self.assertNotIn('Deploy key removed', output.data) + + data = {'csrf_token': csrf_token} + output = self.app.post( + '/test/dropdeploykey/1', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + self.assertIn( + 'Settings - test - Pagure', output.data) + self.assertIn('

Settings for test

', output.data) + self.assertIn('Deploy key removed', output.data) + + + @patch('pagure.ui.repo.admin_session_timedout') def test_remove_user(self, ast): """ Test the remove_user endpoint. """ ast.return_value = False diff --git a/tests/test_pagure_lib_git.py b/tests/test_pagure_lib_git.py index 9638b27..3512d7b 100644 --- a/tests/test_pagure_lib_git.py +++ b/tests/test_pagure_lib_git.py @@ -149,6 +149,122 @@ repo requests/forks/pingou/test3 os.unlink(outputconf) self.assertFalse(os.path.exists(outputconf)) + def test_write_gitolite_acls_deploykeys(self): + """ Test write_gitolite_acls function to add deploy keys. """ + tests.create_projects(self.session) + + repo = pagure.lib.get_project(self.session, 'test') + # Add two deploy keys (one readonly one push) + msg1 = pagure.lib.add_deploykey_to_project( + session=self.session, + project=repo, + ssh_key='ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAzBMSIlvPRaEiLOTVInErkRIw9CzQQcnslDekAn1jFnGf+SNa1acvbTiATbCX71AA03giKrPxPH79dxcC7aDXerc6zRcKjJs6MAL9PrCjnbyxCKXRNNZU5U9X/DLaaL1b3caB+WD6OoorhS3LTEtKPX8xyjOzhf3OQSzNjhJp5Q==', + pushaccess=False, + user='pingou' + ) + msg2 = pagure.lib.add_deploykey_to_project( + session=self.session, + project=repo, + ssh_key='ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9Xwc2RDzPBhlEDARfHldGjudIVoa04tqT1JVKGQmyllTFz7Rb8CngQL3e7zyNzotnhwYKHdoiLlPkVEiDee4dWMUe48ilqId+FJZQGhyv8fu4BoFdE1AJUVylzmltbLg14VqG5gjTpXgtlrEva9arKwBMHJjRYc8ScaSn3OgyQw==', + pushaccess=True, + user='pingou' + ) + self.session.commit() + self.assertEqual(msg1, 'Deploy key added') + self.assertEqual(msg2, 'Deploy key added') + # Add a forked project + item = pagure.lib.model.Project( + user_id=1, # pingou + name='test3', + description='test project #2', + is_fork=True, + parent_id=1, + hook_token='aaabbbvvv', + ) + self.session.add(item) + self.session.commit() + + outputconf = os.path.join(self.path, 'test_gitolite.conf') + + pagure.lib.git.write_gitolite_acls(self.session, outputconf) + + self.assertTrue(os.path.exists(outputconf)) + + with open(outputconf) as stream: + data = stream.read() + + exp = """ +repo test + R = @all + RW+ = pingou + R = deploykey_test_1 + RW+ = deploykey_test_2 + +repo docs/test + R = @all + RW+ = pingou + R = deploykey_test_1 + RW+ = deploykey_test_2 + +repo tickets/test + RW+ = pingou + R = deploykey_test_1 + RW+ = deploykey_test_2 + +repo requests/test + RW+ = pingou + R = deploykey_test_1 + RW+ = deploykey_test_2 + +repo test2 + R = @all + RW+ = pingou + +repo docs/test2 + R = @all + RW+ = pingou + +repo tickets/test2 + RW+ = pingou + +repo requests/test2 + RW+ = pingou + +repo somenamespace/test3 + R = @all + RW+ = pingou + +repo docs/somenamespace/test3 + R = @all + RW+ = pingou + +repo tickets/somenamespace/test3 + RW+ = pingou + +repo requests/somenamespace/test3 + RW+ = pingou + +repo forks/pingou/test3 + R = @all + RW+ = pingou + +repo docs/forks/pingou/test3 + R = @all + RW+ = pingou + +repo tickets/forks/pingou/test3 + RW+ = pingou + +repo requests/forks/pingou/test3 + RW+ = pingou + +""" + #print data + self.assertEqual(data, exp) + + os.unlink(outputconf) + self.assertFalse(os.path.exists(outputconf)) + def test_write_gitolite_acls_groups(self): """ Test the write_gitolite_acls function of pagure.lib.git with groups. From 82632932136d6d81ba4f404469ff6deecd3ea57f Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Feb 09 2017 13:24:53 +0000 Subject: [PATCH 5/6] Add Alembic migration for deploy keys Signed-off-by: Patrick Uiterwijk --- diff --git a/alembic/versions/8a3b10926153_create_deploy_keys_table.py b/alembic/versions/8a3b10926153_create_deploy_keys_table.py new file mode 100644 index 0000000..6773e7c --- /dev/null +++ b/alembic/versions/8a3b10926153_create_deploy_keys_table.py @@ -0,0 +1,36 @@ +"""Create deploy keys table + +Revision ID: 8a3b10926153 +Revises: 38581a8fbae2 +Create Date: 2017-02-09 12:45:59.553111 + +""" + +# revision identifiers, used by Alembic. +revision = '8a3b10926153' +down_revision = '38581a8fbae2' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table('deploykeys', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('project_id', sa.Integer(), nullable=True), + sa.Column('pushaccess', sa.Boolean(), nullable=False), + sa.Column('public_ssh_key', sa.Text(), nullable=False), + sa.Column('ssh_short_key', sa.Text(), nullable=False), + sa.Column('ssh_search_key', sa.Text(), nullable=False), + sa.Column('creator_user_id', sa.Integer(), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['creator_user_id'], ['users.id'], name=op.f('deploykeys_creator_user_id_fkey'), onupdate='CASCADE'), + sa.ForeignKeyConstraint(['project_id'], ['projects.id'], name=op.f('deploykeys_project_id_fkey'), onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id', name=op.f('deploykeys_pkey')) + ) + op.create_index(op.f('ix_deploykeys_deploykeys_creator_user_id'), 'deploykeys', ['creator_user_id'], unique=False) + + +def downgrade(): + op.drop_index(op.f('ix_deploykeys_deploykeys_creator_user_id'), table_name='deploykeys') + op.drop_table('deploykeys') From 19b63cda94bc32388e10f1fbf28a29ebf464b410 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Feb 09 2017 13:24:53 +0000 Subject: [PATCH 6/6] Deploy keys doc Signed-off-by: Patrick Uiterwijk --- diff --git a/doc/usage/project_settings.rst b/doc/usage/project_settings.rst index 495ee22..1d9f7b4 100644 --- a/doc/usage/project_settings.rst +++ b/doc/usage/project_settings.rst @@ -154,4 +154,10 @@ unique to each project, and they can only be defined in the project settings page. The Tag color can also be customized for a more robust visual representation of the tag. +`Deploy keys` +------------- +Deploy keys are SSH keys that have access to pull/push only to a single +project. +Upon creation, admins can determine whether this particular key has read/write +access or read-only.