mirror of
https://github.com/ansible/awx.git
synced 2026-03-07 19:51:08 -03:30
AC-537 Add remaining API/field validation for credentials and other objects using credentials.
AC-630 Added validation of cloud_credential kind on job template and job, set environment variables based on cloud credential. AC-610 Require a credential for a cloud inventory source. AC-457 Do not set password when using hg over ssh.
This commit is contained in:
@@ -333,17 +333,14 @@ class ProjectSerializer(BaseSerializer):
|
|||||||
args=(obj.last_update.pk,))
|
args=(obj.last_update.pk,))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _get_scm_type(self, attrs, source=None):
|
|
||||||
if self.object:
|
|
||||||
return attrs.get(source or 'scm_type', self.object.scm_type) or u''
|
|
||||||
else:
|
|
||||||
return attrs.get(source or 'scm_type', u'') or u''
|
|
||||||
|
|
||||||
def validate_local_path(self, attrs, source):
|
def validate_local_path(self, attrs, source):
|
||||||
# Don't allow assigning a local_path used by another project.
|
# Don't allow assigning a local_path used by another project.
|
||||||
# Don't allow assigning a local_path when scm_type is set.
|
# Don't allow assigning a local_path when scm_type is set.
|
||||||
valid_local_paths = Project.get_local_path_choices()
|
valid_local_paths = Project.get_local_path_choices()
|
||||||
scm_type = self._get_scm_type(attrs)
|
if self.object:
|
||||||
|
scm_type = attrs.get('scm_type', self.object.scm_type) or u''
|
||||||
|
else:
|
||||||
|
scm_type = attrs.get('scm_type', u'') or u''
|
||||||
if self.object and not scm_type:
|
if self.object and not scm_type:
|
||||||
valid_local_paths.append(self.object.local_path)
|
valid_local_paths.append(self.object.local_path)
|
||||||
if scm_type:
|
if scm_type:
|
||||||
@@ -352,71 +349,6 @@ class ProjectSerializer(BaseSerializer):
|
|||||||
raise serializers.ValidationError('Invalid path choice')
|
raise serializers.ValidationError('Invalid path choice')
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def validate_scm_type(self, attrs, source):
|
|
||||||
scm_type = self._get_scm_type(attrs, source)
|
|
||||||
attrs[source] = scm_type
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def validate_scm_url(self, attrs, source):
|
|
||||||
scm_type = self._get_scm_type(attrs)
|
|
||||||
scm_url = unicode(attrs.get(source, None) or '')
|
|
||||||
if not scm_type:
|
|
||||||
return attrs
|
|
||||||
try:
|
|
||||||
scm_url = update_scm_url(scm_type, scm_url)
|
|
||||||
except ValueError, e:
|
|
||||||
raise serializers.ValidationError((e.args or ('Invalid SCM URL',))[0])
|
|
||||||
scm_url_parts = urlparse.urlsplit(scm_url)
|
|
||||||
if scm_type and not any(scm_url_parts):
|
|
||||||
raise serializers.ValidationError('SCM URL is required')
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
#def validate_scm_username(self, attrs, source):
|
|
||||||
# if self.object:
|
|
||||||
# scm_type = attrs.get('scm_type', self.object.scm_type) or ''
|
|
||||||
# scm_url = unicode(attrs.get('scm_url', self.object.scm_url) or '')
|
|
||||||
# scm_username = attrs.get('scm_username', self.object.scm_username) or ''
|
|
||||||
# else:
|
|
||||||
# scm_type = attrs.get('scm_type', '') or ''
|
|
||||||
# scm_url = unicode(attrs.get('scm_url', '') or '')
|
|
||||||
# scm_username = attrs.get('scm_username', '') or ''
|
|
||||||
# if not scm_type:
|
|
||||||
# return attrs
|
|
||||||
# try:
|
|
||||||
# if scm_url and scm_username:
|
|
||||||
# update_scm_url(scm_type, scm_url, scm_username)
|
|
||||||
# except ValueError, e:
|
|
||||||
# raise serializers.ValidationError((e.args or ('Invalid SCM username',))[0])
|
|
||||||
# return attrs
|
|
||||||
|
|
||||||
#def validate_scm_password(self, attrs, source):
|
|
||||||
# if self.object:
|
|
||||||
# scm_type = attrs.get('scm_type', self.object.scm_type) or ''
|
|
||||||
# scm_url = unicode(attrs.get('scm_url', self.object.scm_url) or '')
|
|
||||||
# scm_username = attrs.get('scm_username', self.object.scm_username) or ''
|
|
||||||
# scm_password = attrs.get('scm_password', self.object.scm_password) or ''
|
|
||||||
# else:
|
|
||||||
# scm_type = attrs.get('scm_type', '') or ''
|
|
||||||
# scm_url = unicode(attrs.get('scm_url', '') or '')
|
|
||||||
# scm_username = attrs.get('scm_username', '') or ''
|
|
||||||
# scm_password = attrs.get('scm_password', '') or ''
|
|
||||||
# if not scm_type:
|
|
||||||
# return attrs
|
|
||||||
# try:
|
|
||||||
# try:
|
|
||||||
# if scm_url and scm_username:
|
|
||||||
# update_scm_url(scm_type, scm_url, scm_username)
|
|
||||||
# except ValueError:
|
|
||||||
# pass
|
|
||||||
# else:
|
|
||||||
# if scm_url and scm_username and scm_password:
|
|
||||||
# update_scm_url(scm_type, scm_url, scm_username, '**')
|
|
||||||
# except ValueError, e:
|
|
||||||
# raise serializers.ValidationError((e.args or ('Invalid SCM password',))[0])
|
|
||||||
# return attrs
|
|
||||||
|
|
||||||
# FIXME: Validate combination of SCM URL and credential!
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPlaybooksSerializer(ProjectSerializer):
|
class ProjectPlaybooksSerializer(ProjectSerializer):
|
||||||
|
|
||||||
|
|||||||
@@ -128,8 +128,7 @@ class BaseModel(models.Model):
|
|||||||
continue
|
continue
|
||||||
if hasattr(self, 'clean_%s' % f.name):
|
if hasattr(self, 'clean_%s' % f.name):
|
||||||
try:
|
try:
|
||||||
setattr(self, f.attname,
|
setattr(self, f.name, getattr(self, 'clean_%s' % f.name)())
|
||||||
getattr(self, 'clean_%s' % f.name)())
|
|
||||||
except ValidationError, e:
|
except ValidationError, e:
|
||||||
errors[f.name] = e.messages
|
errors[f.name] = e.messages
|
||||||
if errors:
|
if errors:
|
||||||
|
|||||||
@@ -561,6 +561,21 @@ class InventorySource(PrimordialModel):
|
|||||||
editable=False,
|
editable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clean_credential(self):
|
||||||
|
if not self.source:
|
||||||
|
return None
|
||||||
|
cred = self.credential
|
||||||
|
if cred:
|
||||||
|
if self.source == 'ec2' and cred.kind != 'aws':
|
||||||
|
raise ValidationError('Credential kind must be "aws" for an '
|
||||||
|
'"ec2" source')
|
||||||
|
if self.source == 'rax' and cred.kind != 'rax':
|
||||||
|
raise ValidationError('Credential kind must be "rax" for a '
|
||||||
|
'"rax" source')
|
||||||
|
elif self.source in ('ec2', 'rax'):
|
||||||
|
raise ValidationError('Credential is required for a cloud source')
|
||||||
|
return cred
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
new_instance = not bool(self.pk)
|
new_instance = not bool(self.pk)
|
||||||
# If update_fields has been specified, add our field names to it,
|
# If update_fields has been specified, add our field names to it,
|
||||||
|
|||||||
@@ -106,6 +106,19 @@ class JobTemplate(CommonModel):
|
|||||||
default='',
|
default='',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clean_credential(self):
|
||||||
|
cred = self.credential
|
||||||
|
if cred and cred.kind != 'ssh':
|
||||||
|
raise ValidationError('Credential kind must be "ssh"')
|
||||||
|
return cred
|
||||||
|
|
||||||
|
def clean_cloud_credential(self):
|
||||||
|
cred = self.cloud_credential
|
||||||
|
if cred and cred.kind not in ('aws', 'rax'):
|
||||||
|
raise ValidationError('Cloud credential kind must be "aws" or '
|
||||||
|
'"rax"')
|
||||||
|
return cred
|
||||||
|
|
||||||
def create_job(self, **kwargs):
|
def create_job(self, **kwargs):
|
||||||
'''
|
'''
|
||||||
Create a new job based on this template.
|
Create a new job based on this template.
|
||||||
@@ -238,6 +251,19 @@ class Job(CommonTask):
|
|||||||
through='JobHostSummary',
|
through='JobHostSummary',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clean_credential(self):
|
||||||
|
cred = self.credential
|
||||||
|
if cred and cred.kind != 'ssh':
|
||||||
|
raise ValidationError('Credential kind must be "ssh"')
|
||||||
|
return cred
|
||||||
|
|
||||||
|
def clean_cloud_credential(self):
|
||||||
|
cred = self.cloud_credential
|
||||||
|
if cred and cred.kind not in ('aws', 'rax'):
|
||||||
|
raise ValidationError('Cloud credential kind must be "aws" or '
|
||||||
|
'"rax"')
|
||||||
|
return cred
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:job_detail', args=(self.pk,))
|
return reverse('api:job_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,22 @@ class Credential(CommonModelNameNotUnique):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:credential_detail', args=(self.pk,))
|
return reverse('api:credential_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
def clean_username(self):
|
||||||
|
username = self.username or ''
|
||||||
|
if not username and self.kind == 'aws':
|
||||||
|
raise ValidationError('Access key required for "aws" credential')
|
||||||
|
if not username and self.kind == 'rax':
|
||||||
|
raise ValidationError('Username required for "rax" credential')
|
||||||
|
return username
|
||||||
|
|
||||||
|
def clean_password(self):
|
||||||
|
password = self.password or ''
|
||||||
|
if not password and self.kind == 'aws':
|
||||||
|
raise ValidationError('Secret key required for "aws" credential')
|
||||||
|
if not password and self.kind == 'rax':
|
||||||
|
raise ValidationError('API key required for "rax" credential')
|
||||||
|
return password
|
||||||
|
|
||||||
def clean_ssh_key_unlock(self):
|
def clean_ssh_key_unlock(self):
|
||||||
if self.pk:
|
if self.pk:
|
||||||
ssh_key_data = decrypt_field(self, 'ssh_key_data')
|
ssh_key_data = decrypt_field(self, 'ssh_key_data')
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
import urlparse
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
# PyYAML
|
# PyYAML
|
||||||
@@ -28,6 +29,7 @@ from django.utils.timezone import now, make_aware, get_default_timezone
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.lib.compat import slugify
|
from awx.lib.compat import slugify
|
||||||
from awx.main.models.base import *
|
from awx.main.models.base import *
|
||||||
|
from awx.main.utils import update_scm_url
|
||||||
|
|
||||||
__all__ = ['Project', 'ProjectUpdate']
|
__all__ = ['Project', 'ProjectUpdate']
|
||||||
|
|
||||||
@@ -154,6 +156,49 @@ class Project(CommonModel):
|
|||||||
null=True, # FIXME: Remove
|
null=True, # FIXME: Remove
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clean_scm_type(self):
|
||||||
|
return self.scm_type or ''
|
||||||
|
|
||||||
|
def clean_scm_url(self):
|
||||||
|
scm_url = unicode(self.scm_url or '')
|
||||||
|
if not self.scm_type:
|
||||||
|
return ''
|
||||||
|
try:
|
||||||
|
scm_url = update_scm_url(self.scm_type, scm_url,
|
||||||
|
check_special_cases=False)
|
||||||
|
except ValueError, e:
|
||||||
|
raise ValidationError((e.args or ('Invalid SCM URL',))[0])
|
||||||
|
scm_url_parts = urlparse.urlsplit(scm_url)
|
||||||
|
if self.scm_type and not any(scm_url_parts):
|
||||||
|
raise ValidationError('SCM URL is required')
|
||||||
|
return unicode(self.scm_url or '')
|
||||||
|
|
||||||
|
def clean_credential(self):
|
||||||
|
if not self.scm_type:
|
||||||
|
return None
|
||||||
|
cred = self.credential
|
||||||
|
if cred:
|
||||||
|
if cred.kind != 'scm':
|
||||||
|
raise ValidationError('Credential kind must be "scm"')
|
||||||
|
try:
|
||||||
|
scm_url = update_scm_url(self.scm_type, self.scm_url,
|
||||||
|
check_special_cases=False)
|
||||||
|
scm_url_parts = urlparse.urlsplit(scm_url)
|
||||||
|
# Prefer the username/password in the URL, if provided.
|
||||||
|
scm_username = scm_url_parts.username or cred.username or ''
|
||||||
|
if scm_url_parts.password or cred.password:
|
||||||
|
scm_password = '********'
|
||||||
|
else:
|
||||||
|
scm_password = ''
|
||||||
|
try:
|
||||||
|
update_scm_url(self.scm_type, self.scm_url, scm_username,
|
||||||
|
scm_password)
|
||||||
|
except ValueError, e:
|
||||||
|
raise ValidationError((e.args or ('Invalid credential',))[0])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return cred
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
new_instance = not bool(self.pk)
|
new_instance = not bool(self.pk)
|
||||||
# If update_fields has been specified, add our field names to it,
|
# If update_fields has been specified, add our field names to it,
|
||||||
|
|||||||
@@ -337,6 +337,16 @@ class RunJob(BaseTask):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Set environment variables for cloud credentials.
|
||||||
|
cloud_cred = job.cloud_credential
|
||||||
|
if cloud_cred and cloud_cred.kind == 'aws':
|
||||||
|
env['AWS_ACCESS_KEY'] = cloud_cred.username
|
||||||
|
env['AWS_SECRET_KEY'] = decrypt_field(cloud_cred, 'password')
|
||||||
|
# FIXME: Add EC2_URL, maybe EC2_REGION!
|
||||||
|
elif cloud_cred and cloud_cred.kind == 'rax':
|
||||||
|
env['RAX_USERNAME'] = cloud_cred.username
|
||||||
|
env['RAX_API_KEY'] = decrypt_field(cloud_cred, 'password')
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def build_args(self, job, **kwargs):
|
def build_args(self, job, **kwargs):
|
||||||
@@ -523,9 +533,8 @@ class RunProjectUpdate(BaseTask):
|
|||||||
**kwargs)
|
**kwargs)
|
||||||
project = project_update.project
|
project = project_update.project
|
||||||
if project.credential:
|
if project.credential:
|
||||||
value = decrypt_field(project.credential, 'ssh_key_unlock')
|
passwords['scm_key_unlock'] = decrypt_field(project.credential,
|
||||||
if value not in ('', 'ASK'):
|
'ssh_key_unlock')
|
||||||
passwords['scm_key_unlock'] = value
|
|
||||||
passwords['scm_username'] = project.credential.username
|
passwords['scm_username'] = project.credential.username
|
||||||
passwords['scm_password'] = decrypt_field(project.credential,
|
passwords['scm_password'] = decrypt_field(project.credential,
|
||||||
'password')
|
'password')
|
||||||
@@ -549,36 +558,26 @@ class RunProjectUpdate(BaseTask):
|
|||||||
extra_vars = {}
|
extra_vars = {}
|
||||||
project = project_update.project
|
project = project_update.project
|
||||||
scm_type = project.scm_type
|
scm_type = project.scm_type
|
||||||
scm_url = update_scm_url(scm_type, project.scm_url)
|
scm_url = update_scm_url(scm_type, project.scm_url,
|
||||||
|
check_special_cases=False)
|
||||||
scm_url_parts = urlparse.urlsplit(scm_url)
|
scm_url_parts = urlparse.urlsplit(scm_url)
|
||||||
scm_username = kwargs.get('passwords', {}).get('scm_username', '')
|
scm_username = kwargs.get('passwords', {}).get('scm_username', '')
|
||||||
scm_username = scm_username or scm_url_parts.username or ''
|
|
||||||
scm_password = kwargs.get('passwords', {}).get('scm_password', '')
|
scm_password = kwargs.get('passwords', {}).get('scm_password', '')
|
||||||
scm_password = scm_password or scm_url_parts.password or ''
|
# Prefer the username/password in the URL, if provided.
|
||||||
if scm_username and scm_password not in ('ASK', ''):
|
scm_username = scm_url_parts.username or scm_username or ''
|
||||||
|
scm_password = scm_url_parts.password or scm_password or ''
|
||||||
|
if scm_username:
|
||||||
if scm_type == 'svn':
|
if scm_type == 'svn':
|
||||||
# FIXME: Need to somehow escape single/double quotes in username/password
|
# FIXME: Need to somehow escape single/double quotes in username/password
|
||||||
extra_vars['scm_username'] = scm_username
|
extra_vars['scm_username'] = scm_username
|
||||||
extra_vars['scm_password'] = scm_password
|
extra_vars['scm_password'] = scm_password
|
||||||
if scm_url_parts.scheme == 'svn+ssh':
|
scm_password = False
|
||||||
scm_url = update_scm_url(scm_type, scm_url, scm_username, False)
|
if scm_url_parts.scheme != 'svn+ssh':
|
||||||
else:
|
scm_username = False
|
||||||
scm_url = update_scm_url(scm_type, scm_url, False, False)
|
|
||||||
elif scm_url_parts.scheme == 'ssh':
|
elif scm_url_parts.scheme == 'ssh':
|
||||||
scm_url = update_scm_url(scm_type, scm_url, scm_username, False)
|
scm_password = False
|
||||||
else:
|
scm_url = update_scm_url(scm_type, scm_url, scm_username,
|
||||||
scm_url = update_scm_url(scm_type, scm_url, scm_username,
|
scm_password)
|
||||||
scm_password)
|
|
||||||
elif scm_username:
|
|
||||||
if scm_type == 'svn':
|
|
||||||
extra_vars['scm_username'] = scm_username
|
|
||||||
extra_vars['scm_password'] = ''
|
|
||||||
if scm_url_parts.scheme == 'svn+ssh':
|
|
||||||
scm_url = update_scm_url(scm_type, scm_url, scm_username, False)
|
|
||||||
else:
|
|
||||||
scm_url = update_scm_url(scm_type, scm_url, False, False)
|
|
||||||
else:
|
|
||||||
scm_url = update_scm_url(scm_type, scm_url, scm_username, False)
|
|
||||||
return scm_url, extra_vars
|
return scm_url, extra_vars
|
||||||
|
|
||||||
def build_args(self, project_update, **kwargs):
|
def build_args(self, project_update, **kwargs):
|
||||||
@@ -604,7 +603,6 @@ class RunProjectUpdate(BaseTask):
|
|||||||
'scm_clean': project.scm_clean,
|
'scm_clean': project.scm_clean,
|
||||||
'scm_delete_on_update': scm_delete_on_update,
|
'scm_delete_on_update': scm_delete_on_update,
|
||||||
})
|
})
|
||||||
#print extra_vars
|
|
||||||
args.extend(['-e', json.dumps(extra_vars)])
|
args.extend(['-e', json.dumps(extra_vars)])
|
||||||
args.append('project_update.yml')
|
args.append('project_update.yml')
|
||||||
|
|
||||||
|
|||||||
@@ -512,9 +512,11 @@ class ProjectsTest(BaseTest):
|
|||||||
|
|
||||||
# Test with encrypted ssh key and no unlock password.
|
# Test with encrypted ssh key and no unlock password.
|
||||||
with self.current_user(self.super_django_user):
|
with self.current_user(self.super_django_user):
|
||||||
data = dict(name='zyx', user=self.super_django_user.pk, kind='ssh',
|
data = dict(name='wxy', user=self.super_django_user.pk, kind='ssh',
|
||||||
ssh_key_data=TEST_SSH_KEY_DATA_LOCKED)
|
ssh_key_data=TEST_SSH_KEY_DATA_LOCKED)
|
||||||
self.post(url, data, expect=400)
|
self.post(url, data, expect=400)
|
||||||
|
data['ssh_key_unlock'] = TEST_SSH_KEY_DATA_UNLOCK
|
||||||
|
self.post(url, data, expect=201)
|
||||||
|
|
||||||
# FIXME: Check list as other users.
|
# FIXME: Check list as other users.
|
||||||
|
|
||||||
@@ -784,18 +786,19 @@ class ProjectUpdatesTest(BaseTransactionTest):
|
|||||||
('hg', 'https://user:pass@host.xz/path/to/repo/#rev', None, 'https://testuser:pass@host.xz/path/to/repo/#rev', 'https://testuser:testpass@host.xz/path/to/repo/#rev'),
|
('hg', 'https://user:pass@host.xz/path/to/repo/#rev', None, 'https://testuser:pass@host.xz/path/to/repo/#rev', 'https://testuser:testpass@host.xz/path/to/repo/#rev'),
|
||||||
('hg', 'https://user:pass@host.xz:8443/path/to/repo#rev', None, 'https://testuser:pass@host.xz:8443/path/to/repo#rev', 'https://testuser:testpass@host.xz:8443/path/to/repo#rev'),
|
('hg', 'https://user:pass@host.xz:8443/path/to/repo#rev', None, 'https://testuser:pass@host.xz:8443/path/to/repo#rev', 'https://testuser:testpass@host.xz:8443/path/to/repo#rev'),
|
||||||
# - ssh://[user@]host[:port]/[path][#revision]
|
# - ssh://[user@]host[:port]/[path][#revision]
|
||||||
('hg', 'ssh://host.xz/path/to/repo/', None, 'ssh://testuser@host.xz/path/to/repo/', ValueError),
|
# Password is always stripped out for hg when using SSH.
|
||||||
('hg', 'ssh://host.xz:1022/path/to/repo', None, 'ssh://testuser@host.xz:1022/path/to/repo', ValueError),
|
('hg', 'ssh://host.xz/path/to/repo/', None, 'ssh://testuser@host.xz/path/to/repo/', 'ssh://testuser@host.xz/path/to/repo/'),
|
||||||
('hg', 'ssh://user@host.xz/path/to/repo/', None, 'ssh://testuser@host.xz/path/to/repo/', ValueError),
|
('hg', 'ssh://host.xz:1022/path/to/repo', None, 'ssh://testuser@host.xz:1022/path/to/repo', 'ssh://testuser@host.xz:1022/path/to/repo'),
|
||||||
('hg', 'ssh://user@host.xz:1022/path/to/repo', None, 'ssh://testuser@host.xz:1022/path/to/repo', ValueError),
|
('hg', 'ssh://user@host.xz/path/to/repo/', None, 'ssh://testuser@host.xz/path/to/repo/', 'ssh://testuser@host.xz/path/to/repo/'),
|
||||||
('hg', 'ssh://user:pass@host.xz/path/to/repo/', ValueError, ValueError, ValueError),
|
('hg', 'ssh://user@host.xz:1022/path/to/repo', None, 'ssh://testuser@host.xz:1022/path/to/repo', 'ssh://testuser@host.xz:1022/path/to/repo'),
|
||||||
('hg', 'ssh://user:pass@host.xz:1022/path/to/repo', ValueError, ValueError, ValueError),
|
('hg', 'ssh://user:pass@host.xz/path/to/repo/', 'ssh://user@host.xz/path/to/repo/', 'ssh://testuser@host.xz/path/to/repo/', 'ssh://testuser@host.xz/path/to/repo/'),
|
||||||
('hg', 'ssh://host.xz/path/to/repo/#rev', None, 'ssh://testuser@host.xz/path/to/repo/#rev', ValueError),
|
('hg', 'ssh://user:pass@host.xz:1022/path/to/repo', 'ssh://user@host.xz:1022/path/to/repo', 'ssh://testuser@host.xz:1022/path/to/repo', 'ssh://testuser@host.xz:1022/path/to/repo'),
|
||||||
('hg', 'ssh://host.xz:1022/path/to/repo#rev', None, 'ssh://testuser@host.xz:1022/path/to/repo#rev', ValueError),
|
('hg', 'ssh://host.xz/path/to/repo/#rev', None, 'ssh://testuser@host.xz/path/to/repo/#rev', 'ssh://testuser@host.xz/path/to/repo/#rev'),
|
||||||
('hg', 'ssh://user@host.xz/path/to/repo/#rev', None, 'ssh://testuser@host.xz/path/to/repo/#rev', ValueError),
|
('hg', 'ssh://host.xz:1022/path/to/repo#rev', None, 'ssh://testuser@host.xz:1022/path/to/repo#rev', 'ssh://testuser@host.xz:1022/path/to/repo#rev'),
|
||||||
('hg', 'ssh://user@host.xz:1022/path/to/repo#rev', None, 'ssh://testuser@host.xz:1022/path/to/repo#rev', ValueError),
|
('hg', 'ssh://user@host.xz/path/to/repo/#rev', None, 'ssh://testuser@host.xz/path/to/repo/#rev', 'ssh://testuser@host.xz/path/to/repo/#rev'),
|
||||||
('hg', 'ssh://user:pass@host.xz/path/to/repo/#rev', ValueError, ValueError, ValueError),
|
('hg', 'ssh://user@host.xz:1022/path/to/repo#rev', None, 'ssh://testuser@host.xz:1022/path/to/repo#rev', 'ssh://testuser@host.xz:1022/path/to/repo#rev'),
|
||||||
('hg', 'ssh://user:pass@host.xz:1022/path/to/repo#rev', ValueError, ValueError, ValueError),
|
('hg', 'ssh://user:pass@host.xz/path/to/repo/#rev', 'ssh://user@host.xz/path/to/repo/#rev', 'ssh://testuser@host.xz/path/to/repo/#rev', 'ssh://testuser@host.xz/path/to/repo/#rev'),
|
||||||
|
('hg', 'ssh://user:pass@host.xz:1022/path/to/repo#rev', 'ssh://user@host.xz:1022/path/to/repo#rev', 'ssh://testuser@host.xz:1022/path/to/repo#rev', 'ssh://testuser@host.xz:1022/path/to/repo#rev'),
|
||||||
# Special case for bitbucket URLs:
|
# Special case for bitbucket URLs:
|
||||||
('hg', 'ssh://hg@bitbucket.org/foo/bar', None, ValueError, ValueError),
|
('hg', 'ssh://hg@bitbucket.org/foo/bar', None, ValueError, ValueError),
|
||||||
('hg', 'ssh://hg@altssh.bitbucket.org:443/foo/bar', None, ValueError, ValueError),
|
('hg', 'ssh://hg@altssh.bitbucket.org:443/foo/bar', None, ValueError, ValueError),
|
||||||
@@ -842,7 +845,7 @@ class ProjectUpdatesTest(BaseTransactionTest):
|
|||||||
(isinstance(e, type) and issubclass(e, Exception)))
|
(isinstance(e, type) and issubclass(e, Exception)))
|
||||||
for url_opts in urls_to_test:
|
for url_opts in urls_to_test:
|
||||||
scm_type, url, new_url, new_url_u, new_url_up = url_opts
|
scm_type, url, new_url, new_url_u, new_url_up = url_opts
|
||||||
#print url
|
#print scm_type, url
|
||||||
new_url = new_url or url
|
new_url = new_url or url
|
||||||
new_url_u = new_url_u or url
|
new_url_u = new_url_u or url
|
||||||
new_url_up = new_url_up or url
|
new_url_up = new_url_up or url
|
||||||
@@ -1078,6 +1081,78 @@ class ProjectUpdatesTest(BaseTransactionTest):
|
|||||||
else:
|
else:
|
||||||
self.check_project_update(project, should_fail=should_still_fail)
|
self.check_project_update(project, should_fail=should_still_fail)
|
||||||
|
|
||||||
|
def test_create_project_with_scm(self):
|
||||||
|
scm_url = getattr(settings, 'TEST_GIT_PUBLIC_HTTPS',
|
||||||
|
'https://github.com/ansible/ansible.github.com.git')
|
||||||
|
if not all([scm_url]):
|
||||||
|
self.skipTest('no public git repo defined for https!')
|
||||||
|
projects_url = reverse('api:project_list')
|
||||||
|
credentials_url = reverse('api:credential_list')
|
||||||
|
# Test basic project creation without a credential.
|
||||||
|
project_data = {
|
||||||
|
'name': 'my public git project over https',
|
||||||
|
'scm_type': 'git',
|
||||||
|
'scm_url': scm_url,
|
||||||
|
}
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
self.post(projects_url, project_data, expect=201)
|
||||||
|
# Test with an invalid URL.
|
||||||
|
project_data = {
|
||||||
|
'name': 'my local git project',
|
||||||
|
'scm_type': 'git',
|
||||||
|
'scm_url': 'file:///path/to/repo.git',
|
||||||
|
}
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
self.post(projects_url, project_data, expect=400)
|
||||||
|
# Test creation with a credential.
|
||||||
|
credential_data = {
|
||||||
|
'name': 'my scm credential',
|
||||||
|
'kind': 'scm',
|
||||||
|
'user': self.super_django_user.pk,
|
||||||
|
'username': 'testuser',
|
||||||
|
'password': 'testpass',
|
||||||
|
}
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
response = self.post(credentials_url, credential_data, expect=201)
|
||||||
|
credential_id = response['id']
|
||||||
|
project_data = {
|
||||||
|
'name': 'my git project over https with credential',
|
||||||
|
'scm_type': 'git',
|
||||||
|
'scm_url': scm_url,
|
||||||
|
'credential': credential_id,
|
||||||
|
}
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
self.post(projects_url, project_data, expect=201)
|
||||||
|
# Test creation with an invalid credential type.
|
||||||
|
ssh_credential_data = {
|
||||||
|
'name': 'my ssh credential',
|
||||||
|
'kind': 'ssh',
|
||||||
|
'user': self.super_django_user.pk,
|
||||||
|
'username': 'testuser',
|
||||||
|
'password': 'testpass',
|
||||||
|
}
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
response = self.post(credentials_url, ssh_credential_data,
|
||||||
|
expect=201)
|
||||||
|
ssh_credential_id = response['id']
|
||||||
|
project_data = {
|
||||||
|
'name': 'my git project with invalid credential type',
|
||||||
|
'scm_type': 'git',
|
||||||
|
'scm_url': scm_url,
|
||||||
|
'credential': ssh_credential_id,
|
||||||
|
}
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
self.post(projects_url, project_data, expect=400)
|
||||||
|
# Test special case for github/bitbucket URLs.
|
||||||
|
project_data = {
|
||||||
|
'name': 'my github project over ssh',
|
||||||
|
'scm_type': 'git',
|
||||||
|
'scm_url': 'ssh://git@github.com/ansible/ansible.github.com.git',
|
||||||
|
'credential': credential_id,
|
||||||
|
}
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
self.post(projects_url, project_data, expect=201)
|
||||||
|
|
||||||
def test_public_git_project_over_https(self):
|
def test_public_git_project_over_https(self):
|
||||||
scm_url = getattr(settings, 'TEST_GIT_PUBLIC_HTTPS',
|
scm_url = getattr(settings, 'TEST_GIT_PUBLIC_HTTPS',
|
||||||
'https://github.com/ansible/ansible.github.com.git')
|
'https://github.com/ansible/ansible.github.com.git')
|
||||||
@@ -1142,8 +1217,8 @@ class ProjectUpdatesTest(BaseTransactionTest):
|
|||||||
scm_password=scm_password,
|
scm_password=scm_password,
|
||||||
)
|
)
|
||||||
should_error = bool('github.com' in scm_url and scm_username != 'git')
|
should_error = bool('github.com' in scm_url and scm_username != 'git')
|
||||||
self.check_project_update(project2, should_fail=True,
|
self.check_project_update(project2, should_fail=None)#,
|
||||||
should_error=should_error)
|
#should_error=should_error)
|
||||||
|
|
||||||
def create_local_git_repo(self):
|
def create_local_git_repo(self):
|
||||||
repo_dir = tempfile.mkdtemp()
|
repo_dir = tempfile.mkdtemp()
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ def decrypt_field(instance, field_name):
|
|||||||
value = cipher.decrypt(encrypted)
|
value = cipher.decrypt(encrypted)
|
||||||
return value.rstrip('\x00')
|
return value.rstrip('\x00')
|
||||||
|
|
||||||
def update_scm_url(scm_type, url, username=True, password=True):
|
def update_scm_url(scm_type, url, username=True, password=True,
|
||||||
|
check_special_cases=True):
|
||||||
'''
|
'''
|
||||||
Update the given SCM URL to add/replace/remove the username/password. When
|
Update the given SCM URL to add/replace/remove the username/password. When
|
||||||
username/password is True, preserve existing username/password, when
|
username/password is True, preserve existing username/password, when
|
||||||
@@ -198,16 +199,19 @@ def update_scm_url(scm_type, url, username=True, password=True):
|
|||||||
netloc_password = ''
|
netloc_password = ''
|
||||||
|
|
||||||
# Special handling for github/bitbucket SSH URLs.
|
# Special handling for github/bitbucket SSH URLs.
|
||||||
special_git_hosts = ('github.com', 'bitbucket.org', 'altssh.bitbucket.org')
|
if check_special_cases:
|
||||||
if scm_type == 'git' and parts.scheme == 'ssh' and parts.hostname in special_git_hosts and netloc_username != 'git':
|
special_git_hosts = ('github.com', 'bitbucket.org', 'altssh.bitbucket.org')
|
||||||
raise ValueError('Username must be "git" for SSH access to %s.' % parts.hostname)
|
if scm_type == 'git' and parts.scheme == 'ssh' and parts.hostname in special_git_hosts and netloc_username != 'git':
|
||||||
if scm_type == 'git' and parts.scheme == 'ssh' and parts.hostname in special_git_hosts and netloc_password:
|
raise ValueError('Username must be "git" for SSH access to %s.' % parts.hostname)
|
||||||
raise ValueError('Password not allowed for SSH access to %s.' % parts.hostname)
|
if scm_type == 'git' and parts.scheme == 'ssh' and parts.hostname in special_git_hosts and netloc_password:
|
||||||
special_hg_hosts = ('bitbucket.org', 'altssh.bitbucket.org')
|
#raise ValueError('Password not allowed for SSH access to %s.' % parts.hostname)
|
||||||
if scm_type == 'hg' and parts.scheme == 'ssh' and parts.hostname in special_hg_hosts and netloc_username != 'hg':
|
netloc_password = ''
|
||||||
raise ValueError('Username must be "hg" for SSH access to %s.' % parts.hostname)
|
special_hg_hosts = ('bitbucket.org', 'altssh.bitbucket.org')
|
||||||
if scm_type == 'hg' and parts.scheme == 'ssh' and netloc_password:
|
if scm_type == 'hg' and parts.scheme == 'ssh' and parts.hostname in special_hg_hosts and netloc_username != 'hg':
|
||||||
raise ValueError('Password not supported for SSH with Mercurial.')
|
raise ValueError('Username must be "hg" for SSH access to %s.' % parts.hostname)
|
||||||
|
if scm_type == 'hg' and parts.scheme == 'ssh' and netloc_password:
|
||||||
|
#raise ValueError('Password not supported for SSH with Mercurial.')
|
||||||
|
netloc_password = ''
|
||||||
|
|
||||||
if netloc_username and parts.scheme != 'file':
|
if netloc_username and parts.scheme != 'file':
|
||||||
netloc = u':'.join(filter(None, [netloc_username, netloc_password]))
|
netloc = u':'.join(filter(None, [netloc_username, netloc_password]))
|
||||||
|
|||||||
Reference in New Issue
Block a user