#1873 Implement Deploy Keys
Merged 7 years ago by pingou. Opened 7 years ago by puiterwijk.
puiterwijk/pagure deploykeys  into  master

file modified
+1 -1
@@ -33,7 +33,7 @@ 

  

  # 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:

@@ -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')

@@ -154,4 +154,10 @@ 

  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.

file modified
+14
@@ -419,6 +419,20 @@ 

      )

  

  

+ class AddDeployKeyForm(PagureForm):

+     ''' Form to add a deploy key to a project. '''

+     ssh_key = wtforms.TextField(

+         'SSH Key <span class="error">*</span>',

+         [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(

file modified
+96 -2
@@ -28,6 +28,7 @@ 

  import subprocess

  import urlparse

  import uuid

+ import werkzeug

  

  import bleach

  import redis
@@ -202,8 +203,10 @@ 

                                  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):
@@ -211,6 +214,51 @@ 

                  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.

  
@@ -824,6 +872,52 @@ 

      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)

file modified
+16
@@ -110,6 +110,22 @@ 

              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:

file modified
+38
@@ -635,6 +635,44 @@ 

          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.

  

@@ -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 %}

+ <link href="{{ url_for('static', filename='selectize.bootstrap3.css') }}"

+   rel="stylesheet" />

+ {% endblock %}

+ 

+ {% block title %}Add deploy key - {{

+     repo.namespace + '/' if repo.namespace }}{{ repo.name }}{% endblock %}

+ 

+ {% block repo %}

+ <div class="row col-sm-6 col-sm-offset-3">

+   <div class="card">

+     <div class="card-header">

+       <strong>Add deploy key to the {{repo.name}} project</strong>

+     </div>

+     <div class="card-block">

+       <form action="{{ url_for('.add_deploykey',

+                        username=username, repo=repo.name,

+                        namespace=repo.namespace) }}" method="post">

+ 

+       <fieldset class="form-group">

+         <label for="ssh_key"><strong>SSH key</strong></label>

+         <textarea class="form-control" name="ssh_key" id="ssh_key">{{ form.ssh_key.data or '' }}</textarea>

+       </fieldset>

+       {{ render_bootstrap_field(form.pushaccess, field_description="Do you want to give this key push access?") }}

+ 

+       <p class="buttons indent">

+         <input type="button" value="Cancel" class="btn btn-secondary" onclick="history.back();">

+         <input type="submit" class="btn btn-primary" value="Add">

+         {{ form.csrf_token }}

+       </p>

+     </form>

+     </div>

+   </div>

+ </div>

+ 

+ {% endblock %}

@@ -462,6 +462,54 @@ 

      {% endif %}

  

  

+     <div class="col-md-8 col-md-offset-2">

+       <div class="card">

+         <div class="card-header">

+           Deploy Keys

+         </div>

+         <div class="card-block">

+ 

+           <p>Below are this projects' deploy keys.</p>

+ 

+           <p>

+             <a href="{{ url_for(

+                   '.add_deploykey',

+                   repo=repo.name,

+                   username=username,

+                   namespace=repo.namespace) }}"

+                 class="btn btn-primary">

+                 add deploy key

+             </a>

+           </p>

+         </div>

+           <ul class="list-group list-group-flush">

+           {% for deploykey in repo.deploykeys %}

+             <li class="list-group-item">

+               {{ deploykey.ssh_short_key }}

+               {% if deploykey.pushaccess %}

+               (PUSH ACCESS)

+               {% endif %}

+               <form class="pull-xs-right" method="POST"

+                 action="{{ url_for(

+                     '.remove_deploykey',

+                     repo=repo.name,

+                     username=username,

+                     namespace=repo.namespace,

+                     keyid=deploykey.id) }}">

+                 <button

+                   onclick="return confirm('You sure you want to remove this deploy key from this project?');"

+                   title="Remove deploy key" class="btn btn-danger btn-sm">

+                   <span class="oi" data-glyph="trash"></span>

+                 </button>

+                 {{ form.csrf_token }}

+               </form>

+             </li>

+           {% endfor %}

+           </ul>

+       </div>

+     </div>

+ 

+ 

      {% if plugins %}

      <div class="col-md-8 col-md-offset-2">

        <div class="card">

file modified
+126
@@ -1518,6 +1518,66 @@ 

          namespace=namespace))

  

  

+ @APP.route('/<repo>/dropdeploykey/<int:keyid>', methods=['POST'])

+ @APP.route('/<namespace>/<repo>/dropdeploykey/<int:keyid>', methods=['POST'])

+ @APP.route('/fork/<username>/<repo>/dropdeploykey/<int:keyid>',

+            methods=['POST'])

+ @APP.route('/fork/<username>/<namespace>/<repo>/dropdeploykey/<int:keyid>',

+            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('/<repo>/dropuser/<int:userid>', methods=['POST'])

  @APP.route('/<namespace>/<repo>/dropuser/<int:userid>', methods=['POST'])

  @APP.route('/fork/<username>/<repo>/dropuser/<int:userid>',
@@ -1577,6 +1637,72 @@ 

          namespace=namespace))

  

  

+ @APP.route('/<repo>/adddeploykey/', methods=('GET', 'POST'))

+ @APP.route('/<repo>/adddeploykey', methods=('GET', 'POST'))

+ @APP.route('/<namespace>/<repo>/adddeploykey/', methods=('GET', 'POST'))

+ @APP.route('/<namespace>/<repo>/adddeploykey', methods=('GET', 'POST'))

+ @APP.route('/fork/<username>/<repo>/adddeploykey/', methods=('GET', 'POST'))

+ @APP.route('/fork/<username>/<repo>/adddeploykey', methods=('GET', 'POST'))

+ @APP.route(

+     '/fork/<username>/<namespace>/<repo>/adddeploykey/',

+     methods=('GET', 'POST'))

+ @APP.route(

+     '/fork/<username>/<namespace>/<repo>/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('/<repo>/adduser/', methods=('GET', 'POST'))

  @APP.route('/<repo>/adduser', methods=('GET', 'POST'))

  @APP.route('/<namespace>/<repo>/adduser/', methods=('GET', 'POST'))

@@ -110,6 +110,109 @@ 

  

  

      @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(

+             '</button>\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('<strong>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('<strong>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('<strong>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(

+                 '<title>Settings - test - Pagure</title>', output.data)

+             self.assertIn('<h3>Settings for test</h3>', 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(

+                 '<title>Settings - test - Pagure</title>', output.data)

+             self.assertIn('<h3>Settings for test</h3>', 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 @@ 

  

  

      @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(

+                 '<title>Settings - test - Pagure</title>', output.data)

+             self.assertIn('<h3>Settings for test</h3>', 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(

+                 '<title>Settings - test - Pagure</title>', output.data)

+             self.assertIn('<h3>Settings for test</h3>', 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(

+                 '<title>Settings - test - Pagure</title>', output.data)

+             self.assertIn('<h3>Settings for test</h3>', 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

@@ -149,6 +149,122 @@ 

          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.

no initial comment

You're no longer check if the output of is_valid_ssh_key is None?

Correct. The None result would tell that the user entered newline twice, so there's an empty line, but we already totally ignore those in the code where we write the keys out.

This may end up being quite long, no?

Do we also do that for regular ssh keys?

This is only used for "Regular" (user) ssh keys. We don't use this code for deploy keys, since those are a single key at any time, with no newlines (we reject any deploy keys with newlines).

Yes. But we need to go through them all to make sure we don't add a duped key. I could add a new column to store just this key ID, and just do a query on that if you prefer.

I think that would be faster, especially if we index it

Maybe comment why this one doesn't work?

Could we check the gitolite.conf file produced when there is a pubkey added to the project?

3 new commits added

  • Add tests for deploy keys
  • Add Deploy Keys
  • Make is_valid_ssh_key return key summary
7 years ago

I'm not seeing where this is being called when we create/add a new key (I'm only seeing called when a key is removed)

3 new commits added

  • Add tests for deploy keys
  • Add Deploy Keys
  • Make is_valid_ssh_key return key summary
7 years ago

rebased

7 years ago

5 new commits added

  • Add Alembic migration for deploy keys
  • Add tests for deploy keys
  • Add Deploy Keys
  • Make is_valid_ssh_key return key summary
  • Fix autogenerating alembic
7 years ago

btw, there are trailing spaces here :)

5 new commits added

  • Add Alembic migration for deploy keys
  • Add tests for deploy keys
  • Add Deploy Keys
  • Make is_valid_ssh_key return key summary
  • Fix autogenerating alembic
7 years ago

5 new commits added

  • Add Alembic migration for deploy keys
  • Add tests for deploy keys
  • Add Deploy Keys
  • Make is_valid_ssh_key return key summary
  • Fix autogenerating alembic
7 years ago

1 new commit added

  • Deploy keys doc
7 years ago

Might need a new line after this one :)

6 new commits added

  • Deploy keys doc
  • Add Alembic migration for deploy keys
  • Add tests for deploy keys
  • Add Deploy Keys
  • Make is_valid_ssh_key return key summary
  • Fix autogenerating alembic
7 years ago

form.ssh_key.data or '' might be needed

6 new commits added

  • Deploy keys doc
  • Add Alembic migration for deploy keys
  • Add tests for deploy keys
  • Add Deploy Keys
  • Make is_valid_ssh_key return key summary
  • Fix autogenerating alembic
7 years ago

It's looking good to me :)

Pull-Request has been merged by pingou

7 years ago