Merge branch 'master' into orgview
14
Dockerfile
|
@ -38,19 +38,13 @@ ADD . .
|
||||||
# Run grunt
|
# Run grunt
|
||||||
RUN cd grunt && grunt
|
RUN cd grunt && grunt
|
||||||
|
|
||||||
ADD conf/init/svlogd_config /svlogd_config
|
|
||||||
ADD conf/init/doupdatelimits.sh /etc/my_init.d/
|
ADD conf/init/doupdatelimits.sh /etc/my_init.d/
|
||||||
ADD conf/init/preplogsdir.sh /etc/my_init.d/
|
ADD conf/init/copy_syslog_config.sh /etc/my_init.d/
|
||||||
ADD conf/init/runmigration.sh /etc/my_init.d/
|
ADD conf/init/runmigration.sh /etc/my_init.d/
|
||||||
|
|
||||||
ADD conf/init/gunicorn_web /etc/service/gunicorn_web
|
ADD conf/init/service/ /etc/service/
|
||||||
ADD conf/init/gunicorn_registry /etc/service/gunicorn_registry
|
|
||||||
ADD conf/init/gunicorn_verbs /etc/service/gunicorn_verbs
|
RUN rm -rf /etc/service/syslog-forwarder
|
||||||
ADD conf/init/nginx /etc/service/nginx
|
|
||||||
ADD conf/init/diffsworker /etc/service/diffsworker
|
|
||||||
ADD conf/init/notificationworker /etc/service/notificationworker
|
|
||||||
ADD conf/init/buildlogsarchiver /etc/service/buildlogsarchiver
|
|
||||||
ADD conf/init/buildmanager /etc/service/buildmanager
|
|
||||||
|
|
||||||
# Download any external libs.
|
# Download any external libs.
|
||||||
RUN mkdir static/fonts static/ldn
|
RUN mkdir static/fonts static/ldn
|
||||||
|
|
1
app.py
|
@ -39,7 +39,6 @@ OVERRIDE_CONFIG_YAML_FILENAME = 'conf/stack/config.yaml'
|
||||||
OVERRIDE_CONFIG_PY_FILENAME = 'conf/stack/config.py'
|
OVERRIDE_CONFIG_PY_FILENAME = 'conf/stack/config.py'
|
||||||
|
|
||||||
OVERRIDE_CONFIG_KEY = 'QUAY_OVERRIDE_CONFIG'
|
OVERRIDE_CONFIG_KEY = 'QUAY_OVERRIDE_CONFIG'
|
||||||
LICENSE_FILENAME = 'conf/stack/license.enc'
|
|
||||||
|
|
||||||
CONFIG_PROVIDER = FileConfigProvider(OVERRIDE_CONFIG_DIRECTORY, 'config.yaml', 'config.py')
|
CONFIG_PROVIDER = FileConfigProvider(OVERRIDE_CONFIG_DIRECTORY, 'config.yaml', 'config.py')
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,8 @@ def _process_basic_auth(auth):
|
||||||
logger.debug('Invalid robot or password for robot: %s' % credentials[0])
|
logger.debug('Invalid robot or password for robot: %s' % credentials[0])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
authenticated = authentication.verify_user(credentials[0], credentials[1])
|
(authenticated, error_message) = authentication.verify_user(credentials[0], credentials[1],
|
||||||
|
basic_auth=True)
|
||||||
|
|
||||||
if authenticated:
|
if authenticated:
|
||||||
logger.debug('Successfully validated user: %s' % authenticated.username)
|
logger.debug('Successfully validated user: %s' % authenticated.username)
|
||||||
|
|
|
@ -157,8 +157,12 @@ class EphemeralBuilderManager(BaseManager):
|
||||||
|
|
||||||
etcd_host = self._manager_config.get('ETCD_HOST', '127.0.0.1')
|
etcd_host = self._manager_config.get('ETCD_HOST', '127.0.0.1')
|
||||||
etcd_port = self._manager_config.get('ETCD_PORT', 2379)
|
etcd_port = self._manager_config.get('ETCD_PORT', 2379)
|
||||||
etcd_auth = self._manager_config.get('ETCD_CERT_AND_KEY', None)
|
|
||||||
etcd_ca_cert = self._manager_config.get('ETCD_CA_CERT', None)
|
etcd_ca_cert = self._manager_config.get('ETCD_CA_CERT', None)
|
||||||
|
|
||||||
|
etcd_auth = self._manager_config.get('ETCD_CERT_AND_KEY', None)
|
||||||
|
if etcd_auth is not None:
|
||||||
|
etcd_auth = tuple(etcd_auth) # Convert YAML list to a tuple
|
||||||
|
|
||||||
etcd_protocol = 'http' if etcd_auth is None else 'https'
|
etcd_protocol = 'http' if etcd_auth is None else 'https'
|
||||||
logger.debug('Connecting to etcd on %s:%s', etcd_host, etcd_port)
|
logger.debug('Connecting to etcd on %s:%s', etcd_host, etcd_port)
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ class BuilderExecutor(object):
|
||||||
manager_hostname=manager_hostname,
|
manager_hostname=manager_hostname,
|
||||||
coreos_channel=coreos_channel,
|
coreos_channel=coreos_channel,
|
||||||
worker_tag=self.executor_config['WORKER_TAG'],
|
worker_tag=self.executor_config['WORKER_TAG'],
|
||||||
|
logentries_token=self.executor_config.get('LOGENTRIES_TOKEN', None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ write_files:
|
||||||
REALM={{ realm }}
|
REALM={{ realm }}
|
||||||
TOKEN={{ token }}
|
TOKEN={{ token }}
|
||||||
SERVER=wss://{{ manager_hostname }}
|
SERVER=wss://{{ manager_hostname }}
|
||||||
|
{% if logentries_token -%}
|
||||||
|
LOGENTRIES_TOKEN={{ logentries_token }}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
update:
|
update:
|
||||||
|
@ -19,6 +22,17 @@ coreos:
|
||||||
group: {{ coreos_channel }}
|
group: {{ coreos_channel }}
|
||||||
|
|
||||||
units:
|
units:
|
||||||
|
- name: systemd-journal-gatewayd.socket
|
||||||
|
command: start
|
||||||
|
enable: yes
|
||||||
|
content: |
|
||||||
|
[Unit]
|
||||||
|
Description=Journal Gateway Service Socket
|
||||||
|
[Socket]
|
||||||
|
ListenStream=/var/run/journald.sock
|
||||||
|
Service=systemd-journal-gatewayd.service
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target
|
||||||
{{ dockersystemd('quay-builder',
|
{{ dockersystemd('quay-builder',
|
||||||
'quay.io/coreos/registry-build-worker',
|
'quay.io/coreos/registry-build-worker',
|
||||||
quay_username,
|
quay_username,
|
||||||
|
@ -29,3 +43,10 @@ coreos:
|
||||||
flattened=True,
|
flattened=True,
|
||||||
restart_policy='no'
|
restart_policy='no'
|
||||||
) | indent(4) }}
|
) | indent(4) }}
|
||||||
|
{% if logentries_token -%}
|
||||||
|
{{ dockersystemd('builder-logs',
|
||||||
|
'quay.io/kelseyhightower/journal-2-logentries',
|
||||||
|
extra_args='--env-file /root/overrides.list -v /run/journald.sock:/run/journald.sock',
|
||||||
|
after_units=['quay-builder.service']
|
||||||
|
) | indent(4) }}
|
||||||
|
{%- endif %}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="146" height="18" fill="#555"/><rect rx="4" x="92" width="54" height="18" fill="#dfb317"/><path fill="#dfb317" d="M92 0h4v18h-4z"/><rect rx="4" width="146" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="47" y="13" fill="#010101" fill-opacity=".3">Docker Image</text><text x="47" y="12">Docker Image</text><text x="118" y="13" fill="#010101" fill-opacity=".3">building</text><text x="118" y="12">building</text></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="117" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="117" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h63v20H0z"/><path fill="#dfb317" d="M63 0h54v20H63z"/><path fill="url(#b)" d="M0 0h117v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="32.5" y="15" fill="#010101" fill-opacity=".3">container</text><text x="32.5" y="14">container</text><text x="89" y="15" fill="#010101" fill-opacity=".3">building</text><text x="89" y="14">building</text></g></svg>
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 748 B |
|
@ -1 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="164" height="18" fill="#555"/><rect rx="4" x="92" width="72" height="18" fill="#e05d44"/><path fill="#e05d44" d="M92 0h4v18h-4z"/><rect rx="4" width="164" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="47" y="13" fill="#010101" fill-opacity=".3">Docker Image</text><text x="47" y="12">Docker Image</text><text x="127" y="13" fill="#010101" fill-opacity=".3">build failed</text><text x="127" y="12">build failed</text></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="104" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h63v20H0z"/><path fill="#e05d44" d="M63 0h41v20H63z"/><path fill="url(#b)" d="M0 0h104v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="32.5" y="15" fill="#010101" fill-opacity=".3">container</text><text x="32.5" y="14">container</text><text x="82.5" y="15" fill="#010101" fill-opacity=".3">failed</text><text x="82.5" y="14">failed</text></g></svg>
|
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 748 B |
|
@ -1 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="130" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="130" height="18" fill="#555"/><rect rx="4" x="92" width="38" height="18" fill="#9f9f9f"/><path fill="#9f9f9f" d="M92 0h4v18h-4z"/><rect rx="4" width="130" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="47" y="13" fill="#010101" fill-opacity=".3">Docker Image</text><text x="47" y="12">Docker Image</text><text x="110" y="13" fill="#010101" fill-opacity=".3">none</text><text x="110" y="12">none</text></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="101" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="101" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h63v20H0z"/><path fill="#9f9f9f" d="M63 0h38v20H63z"/><path fill="url(#b)" d="M0 0h101v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="32.5" y="15" fill="#010101" fill-opacity=".3">container</text><text x="32.5" y="14">container</text><text x="81" y="15" fill="#010101" fill-opacity=".3">none</text><text x="81" y="14">none</text></g></svg>
|
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 740 B |
|
@ -1 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="135" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="135" height="18" fill="#555"/><rect rx="4" x="92" width="43" height="18" fill="#4c1"/><path fill="#4c1" d="M92 0h4v18h-4z"/><rect rx="4" width="135" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="47" y="13" fill="#010101" fill-opacity=".3">Docker Image</text><text x="47" y="12">Docker Image</text><text x="112.5" y="13" fill="#010101" fill-opacity=".3">ready</text><text x="112.5" y="12">ready</text></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="106" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="106" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h63v20H0z"/><path fill="#97CA00" d="M63 0h43v20H63z"/><path fill="url(#b)" d="M0 0h106v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="32.5" y="15" fill="#010101" fill-opacity=".3">container</text><text x="32.5" y="14">container</text><text x="83.5" y="15" fill="#010101" fill-opacity=".3">ready</text><text x="83.5" y="14">ready</text></g></svg>
|
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 746 B |
|
@ -4,7 +4,7 @@ types_hash_max_size 2048;
|
||||||
include /usr/local/nginx/conf/mime.types.default;
|
include /usr/local/nginx/conf/mime.types.default;
|
||||||
|
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
access_log /var/log/nginx/nginx.access.log;
|
access_log /dev/stdout;
|
||||||
sendfile on;
|
sendfile on;
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec svlogd /var/log/buildlogsarchiver/
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec svlogd /var/log/buildmanager/
|
|
6
conf/init/copy_syslog_config.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
if [ -e /conf/stack/syslog-ng-extra.conf ]
|
||||||
|
then
|
||||||
|
cp /conf/stack/syslog-ng-extra.conf /etc/syslog-ng/conf.d/
|
||||||
|
fi
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec svlogd /var/log/diffsworker/
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec svlogd /var/log/gunicorn_registry/
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec svlogd /var/log/gunicorn_verbs/
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec svlogd /var/log/gunicorn_web/
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec svlogd /var/log/nginx/
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
exec svlogd -t /var/log/notificationworker/
|
|
|
@ -1,10 +0,0 @@
|
||||||
#! /bin/sh
|
|
||||||
|
|
||||||
echo 'Linking config files to logs directory'
|
|
||||||
for svc in `ls /etc/service/`
|
|
||||||
do
|
|
||||||
if [ ! -d /var/log/$svc ]; then
|
|
||||||
mkdir -p /var/log/$svc
|
|
||||||
ln -s /svlogd_config /var/log/$svc/config
|
|
||||||
fi
|
|
||||||
done
|
|
2
conf/init/service/buildlogsarchiver/log/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
exec logger -i -t buildlogsarchiver
|
2
conf/init/service/buildmanager/log/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
exec logger -i -t buildmanager
|
2
conf/init/service/diffsworker/log/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
exec logger -i -t diffsworker
|
2
conf/init/service/gunicorn_registry/log/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
exec logger -i -t gunicorn_registry
|
2
conf/init/service/gunicorn_verbs/log/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
exec logger -i -t gunicorn_verbs
|
2
conf/init/service/gunicorn_web/log/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
exec logger -i -t gunicorn_web
|
2
conf/init/service/nginx/log/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
exec logger -i -t nginx
|
2
conf/init/service/notificationworker/log/run
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
exec logger -i -t notificationworker
|
|
@ -1,3 +0,0 @@
|
||||||
s100000000
|
|
||||||
t86400
|
|
||||||
n4
|
|
|
@ -5,4 +5,4 @@ real_ip_header proxy_protocol;
|
||||||
log_format elb_pp '$proxy_protocol_addr - $remote_user [$time_local] '
|
log_format elb_pp '$proxy_protocol_addr - $remote_user [$time_local] '
|
||||||
'"$request" $status $body_bytes_sent '
|
'"$request" $status $body_bytes_sent '
|
||||||
'"$http_referer" "$http_user_agent"';
|
'"$http_referer" "$http_user_agent"';
|
||||||
access_log /var/log/nginx/nginx.access.log elb_pp;
|
access_log /dev/stdout elb_pp;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# vim: ft=nginx
|
# vim: ft=nginx
|
||||||
|
|
||||||
pid /tmp/nginx.pid;
|
pid /tmp/nginx.pid;
|
||||||
error_log /var/log/nginx/nginx.error.log;
|
error_log /dev/stdout;
|
||||||
|
|
||||||
worker_processes 2;
|
worker_processes 2;
|
||||||
worker_priority -10;
|
worker_priority -10;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# vim: ft=nginx
|
# vim: ft=nginx
|
||||||
|
|
||||||
client_body_temp_path /var/log/nginx/client_body 1 2;
|
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
keepalive_timeout 5;
|
keepalive_timeout 5;
|
||||||
|
@ -36,7 +35,7 @@ location /v1/repositories/ {
|
||||||
|
|
||||||
proxy_pass http://registry_app_server;
|
proxy_pass http://registry_app_server;
|
||||||
proxy_read_timeout 2000;
|
proxy_read_timeout 2000;
|
||||||
proxy_temp_path /var/log/nginx/proxy_temp 1 2;
|
proxy_temp_path /tmp 1 2;
|
||||||
|
|
||||||
limit_req zone=repositories burst=10;
|
limit_req zone=repositories burst=10;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +46,7 @@ location /v1/ {
|
||||||
proxy_request_buffering off;
|
proxy_request_buffering off;
|
||||||
|
|
||||||
proxy_pass http://registry_app_server;
|
proxy_pass http://registry_app_server;
|
||||||
proxy_temp_path /var/log/nginx/proxy_temp 1 2;
|
proxy_temp_path /tmp 1 2;
|
||||||
|
|
||||||
client_max_body_size 20G;
|
client_max_body_size 20G;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +57,7 @@ location /c1/ {
|
||||||
proxy_request_buffering off;
|
proxy_request_buffering off;
|
||||||
|
|
||||||
proxy_pass http://verbs_app_server;
|
proxy_pass http://verbs_app_server;
|
||||||
proxy_temp_path /var/log/nginx/proxy_temp 1 2;
|
proxy_temp_path /tmp 1 2;
|
||||||
|
|
||||||
limit_req zone=verbs burst=10;
|
limit_req zone=verbs burst=10;
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,10 @@ class DefaultConfig(object):
|
||||||
# Feature Flag: Whether users can be renamed
|
# Feature Flag: Whether users can be renamed
|
||||||
FEATURE_USER_RENAME = False
|
FEATURE_USER_RENAME = False
|
||||||
|
|
||||||
|
# Feature Flag: Whether non-encrypted passwords (as opposed to encrypted tokens) can be used for
|
||||||
|
# basic auth.
|
||||||
|
FEATURE_REQUIRE_ENCRYPTED_BASIC_AUTH = False
|
||||||
|
|
||||||
BUILD_MANAGER = ('enterprise', {})
|
BUILD_MANAGER = ('enterprise', {})
|
||||||
|
|
||||||
DISTRIBUTED_STORAGE_CONFIG = {
|
DISTRIBUTED_STORAGE_CONFIG = {
|
||||||
|
|
|
@ -950,6 +950,7 @@ def change_password(user, new_password):
|
||||||
pw_hash = hash_password(new_password)
|
pw_hash = hash_password(new_password)
|
||||||
user.invalid_login_attempts = 0
|
user.invalid_login_attempts = 0
|
||||||
user.password_hash = pw_hash
|
user.password_hash = pw_hash
|
||||||
|
user.uuid = str(uuid4())
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
# Remove any password required notifications for the user.
|
# Remove any password required notifications for the user.
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import ldap
|
import ldap
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
|
import itertools
|
||||||
|
import uuid
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from util.aes import AESCipher
|
||||||
from util.validation import generate_valid_usernames
|
from util.validation import generate_valid_usernames
|
||||||
from data import model
|
from data import model
|
||||||
|
|
||||||
|
@ -106,6 +111,7 @@ class LDAPUsers(object):
|
||||||
return found_user is not None
|
return found_user is not None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UserAuthentication(object):
|
class UserAuthentication(object):
|
||||||
def __init__(self, app=None):
|
def __init__(self, app=None):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -138,5 +144,81 @@ class UserAuthentication(object):
|
||||||
app.extensions['authentication'] = users
|
app.extensions['authentication'] = users
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
def _get_secret_key(self):
|
||||||
|
""" Returns the secret key to use for encrypting and decrypting. """
|
||||||
|
from app import app
|
||||||
|
app_secret_key = app.config['SECRET_KEY']
|
||||||
|
secret_key = None
|
||||||
|
|
||||||
|
# First try parsing the key as an int.
|
||||||
|
try:
|
||||||
|
big_int = int(app_secret_key)
|
||||||
|
secret_key = str(bytearray.fromhex('{:02x}'.format(big_int)))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Next try parsing it as an UUID.
|
||||||
|
if secret_key is None:
|
||||||
|
try:
|
||||||
|
secret_key = uuid.UUID(app_secret_key).bytes
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if secret_key is None:
|
||||||
|
secret_key = str(bytearray(map(ord, app_secret_key)))
|
||||||
|
|
||||||
|
# Otherwise, use the bytes directly.
|
||||||
|
return ''.join(itertools.islice(itertools.cycle(secret_key), 32))
|
||||||
|
|
||||||
|
def encrypt_user_password(self, password):
|
||||||
|
""" Returns an encrypted version of the user's password. """
|
||||||
|
data = {
|
||||||
|
'password': password
|
||||||
|
}
|
||||||
|
|
||||||
|
message = json.dumps(data)
|
||||||
|
cipher = AESCipher(self._get_secret_key())
|
||||||
|
return cipher.encrypt(message)
|
||||||
|
|
||||||
|
def _decrypt_user_password(self, encrypted):
|
||||||
|
""" Attempts to decrypt the given password and returns it. """
|
||||||
|
cipher = AESCipher(self._get_secret_key())
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = cipher.decrypt(encrypted)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(message)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return data.get('password', encrypted)
|
||||||
|
|
||||||
|
def verify_user(self, username_or_email, password, basic_auth=False):
|
||||||
|
# First try to decode the password as a signed token.
|
||||||
|
if basic_auth:
|
||||||
|
import features
|
||||||
|
|
||||||
|
decrypted = self._decrypt_user_password(password)
|
||||||
|
if decrypted is None:
|
||||||
|
# This is a normal password.
|
||||||
|
if features.REQUIRE_ENCRYPTED_BASIC_AUTH:
|
||||||
|
msg = ('Client login with unecrypted passwords is disabled. Please generate an ' +
|
||||||
|
'encrypted password in the user admin panel for use here.')
|
||||||
|
return (None, msg)
|
||||||
|
else:
|
||||||
|
password = decrypted
|
||||||
|
|
||||||
|
result = self.state.verify_user(username_or_email, password)
|
||||||
|
if result:
|
||||||
|
return (result, '')
|
||||||
|
else:
|
||||||
|
return (result, 'Invalid password.')
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(self.state, name, None)
|
return getattr(self.state, name, None)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from random import SystemRandom
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask.ext.login import logout_user
|
from flask.ext.login import logout_user
|
||||||
from flask.ext.principal import identity_changed, AnonymousIdentity
|
from flask.ext.principal import identity_changed, AnonymousIdentity
|
||||||
|
@ -224,8 +225,13 @@ class User(ApiResource):
|
||||||
if 'password' in user_data:
|
if 'password' in user_data:
|
||||||
logger.debug('Changing password for user: %s', user.username)
|
logger.debug('Changing password for user: %s', user.username)
|
||||||
log_action('account_change_password', user.username)
|
log_action('account_change_password', user.username)
|
||||||
|
|
||||||
|
# Change the user's password.
|
||||||
model.change_password(user, user_data['password'])
|
model.change_password(user, user_data['password'])
|
||||||
|
|
||||||
|
# Login again to reset their session cookie.
|
||||||
|
common_login(user)
|
||||||
|
|
||||||
if features.MAILING:
|
if features.MAILING:
|
||||||
send_password_changed(user.username, user.email)
|
send_password_changed(user.username, user.email)
|
||||||
|
|
||||||
|
@ -335,13 +341,51 @@ class PrivateRepositories(ApiResource):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@resource('/v1/user/clientkey')
|
||||||
|
@internal_only
|
||||||
|
class ClientKey(ApiResource):
|
||||||
|
""" Operations for returning an encrypted key which can be used in place of a password
|
||||||
|
for the Docker client. """
|
||||||
|
schemas = {
|
||||||
|
'GenerateClientKey': {
|
||||||
|
'id': 'GenerateClientKey',
|
||||||
|
'type': 'object',
|
||||||
|
'required': [
|
||||||
|
'password',
|
||||||
|
],
|
||||||
|
'properties': {
|
||||||
|
'password': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': 'The user\'s password',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@require_user_admin
|
||||||
|
@nickname('generateUserClientKey')
|
||||||
|
@validate_json_request('GenerateClientKey')
|
||||||
|
def post(self):
|
||||||
|
""" Return's the user's private client key. """
|
||||||
|
username = get_authenticated_user().username
|
||||||
|
password = request.get_json()['password']
|
||||||
|
|
||||||
|
(result, error_message) = authentication.verify_user(username, password)
|
||||||
|
if not result:
|
||||||
|
raise request_error(message=error_message)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'key': authentication.encrypt_user_password(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def conduct_signin(username_or_email, password):
|
def conduct_signin(username_or_email, password):
|
||||||
needs_email_verification = False
|
needs_email_verification = False
|
||||||
invalid_credentials = False
|
invalid_credentials = False
|
||||||
|
|
||||||
verified = None
|
verified = None
|
||||||
try:
|
try:
|
||||||
verified = authentication.verify_user(username_or_email, password)
|
(verified, error_message) = authentication.verify_user(username_or_email, password)
|
||||||
except model.TooManyUsersException as ex:
|
except model.TooManyUsersException as ex:
|
||||||
raise license_error(exception=ex)
|
raise license_error(exception=ex)
|
||||||
|
|
||||||
|
@ -407,7 +451,7 @@ class ConvertToOrganization(ApiResource):
|
||||||
|
|
||||||
# Ensure that the sign in credentials work.
|
# Ensure that the sign in credentials work.
|
||||||
admin_password = convert_data['adminPassword']
|
admin_password = convert_data['adminPassword']
|
||||||
admin_user = authentication.verify_user(admin_username, admin_password)
|
(admin_user, error_message) = authentication.verify_user(admin_username, admin_password)
|
||||||
if not admin_user:
|
if not admin_user:
|
||||||
raise request_error(reason='invaliduser',
|
raise request_error(reason='invaliduser',
|
||||||
message='The admin user credentials are not valid')
|
message='The admin user credentials are not valid')
|
||||||
|
|
|
@ -109,7 +109,7 @@ def create_user():
|
||||||
issue='robot-login-failure')
|
issue='robot-login-failure')
|
||||||
|
|
||||||
if authentication.user_exists(username):
|
if authentication.user_exists(username):
|
||||||
verified = authentication.verify_user(username, password)
|
(verified, error_message) = authentication.verify_user(username, password, basic_auth=True)
|
||||||
if verified:
|
if verified:
|
||||||
# Mark that the user was logged in.
|
# Mark that the user was logged in.
|
||||||
event = userevents.get_event(username)
|
event = userevents.get_event(username)
|
||||||
|
@ -121,7 +121,7 @@ def create_user():
|
||||||
event = userevents.get_event(username)
|
event = userevents.get_event(username)
|
||||||
event.publish_event_data('docker-cli', {'action': 'loginfailure'})
|
event.publish_event_data('docker-cli', {'action': 'loginfailure'})
|
||||||
|
|
||||||
abort(400, 'Invalid password.', issue='login-failure')
|
abort(400, error_message, issue='login-failure')
|
||||||
|
|
||||||
elif not features.USER_CREATION:
|
elif not features.USER_CREATION:
|
||||||
abort(400, 'User creation is disabled. Please speak to your administrator.')
|
abort(400, 'User creation is disabled. Please speak to your administrator.')
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>User Creation:</td>
|
<td class="non-input">User Creation:</td>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="co-checkbox">
|
<div class="co-checkbox">
|
||||||
<input id="ftuc" type="checkbox" ng-model="config.FEATURE_USER_CREATION">
|
<input id="ftuc" type="checkbox" ng-model="config.FEATURE_USER_CREATION">
|
||||||
|
@ -46,6 +46,23 @@
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="non-input">Encrypted Client Password:</td>
|
||||||
|
<td colspan="2">
|
||||||
|
<div class="co-checkbox">
|
||||||
|
<input id="ftet" type="checkbox" ng-model="config.FEATURE_REQUIRE_ENCRYPTED_BASIC_AUTH">
|
||||||
|
<label for="ftet">Require Encrypted Client Passwords</label>
|
||||||
|
</div>
|
||||||
|
<div class="help-text">
|
||||||
|
If enabled, users will not be able to login from the Docker command
|
||||||
|
line with a non-encrypted password and must generate an encrypted
|
||||||
|
password to use.
|
||||||
|
</div>
|
||||||
|
<div class="help-text" ng-if="config.AUTHENTICATION_TYPE == 'LDAP'">
|
||||||
|
This feature is <strong>highly recommended</strong> for setups with LDAP authentication, as Docker currently stores passwords in <strong>plaintext</strong> on user's machines.
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -293,6 +310,16 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning" ng-if="config.AUTHENTICATION_TYPE == 'LDAP' && !config.FEATURE_REQUIRE_ENCRYPTED_BASIC_AUTH">
|
||||||
|
It is <strong>highly recommended</strong> to require encrypted client passwords. LDAP passwords used in the Docker client will be stored in <strong>plaintext</strong>!
|
||||||
|
<a href="javascript:void(0)" ng-click="config.FEATURE_REQUIRE_ENCRYPTED_BASIC_AUTH = true">Enable this requirement now</a>.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" ng-if="config.AUTHENTICATION_TYPE == 'LDAP' && config.FEATURE_REQUIRE_ENCRYPTED_BASIC_AUTH">
|
||||||
|
Note: The "Require Encrypted Client Passwords" feature is currently enabled which will
|
||||||
|
prevent LDAP passwords from being saved as plaintext by the Docker client.
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="config-table">
|
<table class="config-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="non-input">Authentication:</td>
|
<td class="non-input">Authentication:</td>
|
||||||
|
@ -305,7 +332,6 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<table class="config-table" ng-if="config.AUTHENTICATION_TYPE == 'LDAP'">
|
<table class="config-table" ng-if="config.AUTHENTICATION_TYPE == 'LDAP'">
|
||||||
<tr>
|
<tr>
|
||||||
<td>LDAP URI:</td>
|
<td>LDAP URI:</td>
|
||||||
|
|
|
@ -196,6 +196,21 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.generateClientToken = function() {
|
||||||
|
var generateToken = function(password) {
|
||||||
|
var data = {
|
||||||
|
'password': password
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiService.generateUserClientKey(data).then(function(resp) {
|
||||||
|
$scope.generatedClientToken = resp['key'];
|
||||||
|
$('#clientTokenModal').modal({});
|
||||||
|
}, ApiService.errorDisplay('Could not generate token'));
|
||||||
|
};
|
||||||
|
|
||||||
|
UIService.showPasswordDialog('Enter your password to generated an encrypted version:', generateToken);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.detachExternalLogin = function(kind) {
|
$scope.detachExternalLogin = function(kind) {
|
||||||
var params = {
|
var params = {
|
||||||
'servicename': kind
|
'servicename': kind
|
||||||
|
|
|
@ -92,5 +92,47 @@ angular.module('quay').factory('UIService', [function() {
|
||||||
return new CheckStateController(items, opt_checked);
|
return new CheckStateController(items, opt_checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
uiService.showPasswordDialog = function(message, callback, opt_canceledCallback) {
|
||||||
|
var success = function() {
|
||||||
|
var password = $('#passDialogBox').val();
|
||||||
|
$('#passDialogBox').val('');
|
||||||
|
callback(password);
|
||||||
|
};
|
||||||
|
|
||||||
|
var canceled = function() {
|
||||||
|
$('#passDialogBox').val('');
|
||||||
|
opt_canceledCallback && opt_canceledCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
var box = bootbox.dialog({
|
||||||
|
"message": message +
|
||||||
|
'<form style="margin-top: 10px" action="javascript:void(0)">' +
|
||||||
|
'<input id="passDialogBox" class="form-control" type="password" placeholder="Current Password">' +
|
||||||
|
'</form>',
|
||||||
|
"title": 'Please Verify',
|
||||||
|
"buttons": {
|
||||||
|
"verify": {
|
||||||
|
"label": "Verify",
|
||||||
|
"className": "btn-success",
|
||||||
|
"callback": success
|
||||||
|
},
|
||||||
|
"close": {
|
||||||
|
"label": "Cancel",
|
||||||
|
"className": "btn-default",
|
||||||
|
"callback": canceled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
box.bind('shown.bs.modal', function(){
|
||||||
|
box.find("input").focus();
|
||||||
|
box.find("form").submit(function() {
|
||||||
|
if (!$('#passDialogBox').val()) { return; }
|
||||||
|
box.modal('hide');
|
||||||
|
success();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return uiService;
|
return uiService;
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<!-- Non-billing -->
|
<!-- Non-billing -->
|
||||||
<li quay-classes="{'!Features.BILLING': 'active'}"><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
|
<li quay-classes="{'!Features.BILLING': 'active'}"><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Change Password</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Password</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#external" quay-show="Features.GITHUB_LOGIN || Features.GOOGLE_LOGIN">External Logins</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#external" quay-show="Features.GITHUB_LOGIN || Features.GOOGLE_LOGIN">External Logins</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#authorized" ng-click="loadAuthedApps()">Authorized Applications</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#authorized" ng-click="loadAuthedApps()">Authorized Applications</a></li>
|
||||||
<li quay-show="Features.USER_LOG_ACCESS || hasPaidBusinessPlan">
|
<li quay-show="Features.USER_LOG_ACCESS || hasPaidBusinessPlan">
|
||||||
|
@ -139,8 +139,31 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Change password tab -->
|
<!-- Password tab -->
|
||||||
<div id="password" class="tab-pane">
|
<div id="password" class="tab-pane">
|
||||||
|
<!-- Encrypted Password -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">Generate Encrypted Password</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="alert alert-info" ng-if="!Features.REQUIRE_ENCRYPTED_BASIC_AUTH">
|
||||||
|
Due to Docker storing passwords entered on the command line in <strong>plaintext</strong>, it is highly recommended to use the button below to generate an an encrypted version of your password.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning" ng-if="Features.REQUIRE_ENCRYPTED_BASIC_AUTH">
|
||||||
|
This installation is set to <strong>require</strong> encrypted passwords when
|
||||||
|
using the Docker command line interface. To generate an encrypted password, click the button below.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" ng-click="generateClientToken()">
|
||||||
|
<i class="fa fa-key" style="margin-right: 6px;"></i>Generate Encrypted Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Change Password -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-title">Change Password</div>
|
<div class="panel-title">Change Password</div>
|
||||||
|
@ -152,6 +175,9 @@
|
||||||
<span class="help-block" ng-show="changePasswordSuccess">Password changed successfully</span>
|
<span class="help-block" ng-show="changePasswordSuccess">Password changed successfully</span>
|
||||||
|
|
||||||
<div ng-show="!updatingUser" class="panel-body">
|
<div ng-show="!updatingUser" class="panel-body">
|
||||||
|
<div class="alert alert-warning">Note: Changing your password will also invalidate any generated encrypted passwords.</div>
|
||||||
|
|
||||||
|
|
||||||
<form class="form-change col-md-6" id="changePasswordForm" name="changePasswordForm" ng-submit="changePassword()"
|
<form class="form-change col-md-6" id="changePasswordForm" name="changePasswordForm" ng-submit="changePassword()"
|
||||||
ng-show="!awaitingConfirmation && !registering">
|
ng-show="!awaitingConfirmation && !registering">
|
||||||
<input type="password" class="form-control" placeholder="Your new password" ng-model="cuser.password" required
|
<input type="password" class="form-control" placeholder="Your new password" ng-model="cuser.password" required
|
||||||
|
@ -370,6 +396,26 @@
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal message dialog -->
|
||||||
|
<div class="modal fade" id="clientTokenModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">Encrypted Password</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div style="margin-bottom: 10px;">Your generated encrypted password:</div>
|
||||||
|
<div class="copy-box" value="generatedClientToken"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Dismiss</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
<!-- Modal message dialog -->
|
<!-- Modal message dialog -->
|
||||||
<div class="modal fade" id="reallyconvertModal">
|
<div class="modal fade" id="reallyconvertModal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
|
|
|
@ -27,7 +27,8 @@ from endpoints.api.repoemail import RepositoryAuthorizedEmail
|
||||||
from endpoints.api.repositorynotification import RepositoryNotification, RepositoryNotificationList
|
from endpoints.api.repositorynotification import RepositoryNotification, RepositoryNotificationList
|
||||||
from endpoints.api.user import (PrivateRepositories, ConvertToOrganization, Recovery, Signout,
|
from endpoints.api.user import (PrivateRepositories, ConvertToOrganization, Recovery, Signout,
|
||||||
Signin, User, UserAuthorizationList, UserAuthorization, UserNotification,
|
Signin, User, UserAuthorizationList, UserAuthorization, UserNotification,
|
||||||
VerifyUser, DetachExternal, StarredRepositoryList, StarredRepository)
|
VerifyUser, DetachExternal, StarredRepositoryList, StarredRepository,
|
||||||
|
ClientKey)
|
||||||
from endpoints.api.repotoken import RepositoryToken, RepositoryTokenList
|
from endpoints.api.repotoken import RepositoryToken, RepositoryTokenList
|
||||||
from endpoints.api.prototype import PermissionPrototype, PermissionPrototypeList
|
from endpoints.api.prototype import PermissionPrototype, PermissionPrototypeList
|
||||||
from endpoints.api.logs import UserLogs, OrgLogs, RepositoryLogs
|
from endpoints.api.logs import UserLogs, OrgLogs, RepositoryLogs
|
||||||
|
@ -529,6 +530,26 @@ class TestVerifyUser(ApiTestCase):
|
||||||
self._run_test('POST', 200, 'devtable', {u'password': 'password'})
|
self._run_test('POST', 200, 'devtable', {u'password': 'password'})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestClientKey(ApiTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
ApiTestCase.setUp(self)
|
||||||
|
self._set_url(ClientKey)
|
||||||
|
|
||||||
|
def test_post_anonymous(self):
|
||||||
|
self._run_test('POST', 401, None, {u'password': 'LQ0N'})
|
||||||
|
|
||||||
|
def test_post_freshuser(self):
|
||||||
|
self._run_test('POST', 400, 'freshuser', {u'password': 'LQ0N'})
|
||||||
|
|
||||||
|
def test_post_reader(self):
|
||||||
|
self._run_test('POST', 200, 'reader', {u'password': 'password'})
|
||||||
|
|
||||||
|
def test_post_devtable(self):
|
||||||
|
self._run_test('POST', 200, 'devtable', {u'password': 'password'})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestListPlans(ApiTestCase):
|
class TestListPlans(ApiTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
ApiTestCase.setUp(self)
|
ApiTestCase.setUp(self)
|
||||||
|
|
32
util/aes.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
from Crypto import Random
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
class AESCipher(object):
|
||||||
|
""" Helper class for encrypting and decrypting data via AES.
|
||||||
|
|
||||||
|
Copied From: http://stackoverflow.com/a/21928790
|
||||||
|
"""
|
||||||
|
def __init__(self, key):
|
||||||
|
self.bs = 32
|
||||||
|
self.key = key
|
||||||
|
|
||||||
|
def encrypt(self, raw):
|
||||||
|
raw = self._pad(raw)
|
||||||
|
iv = Random.new().read(AES.block_size)
|
||||||
|
cipher = AES.new(self.key, AES.MODE_CBC, iv)
|
||||||
|
return base64.b64encode(iv + cipher.encrypt(raw))
|
||||||
|
|
||||||
|
def decrypt(self, enc):
|
||||||
|
enc = base64.b64decode(enc)
|
||||||
|
iv = enc[:AES.block_size]
|
||||||
|
cipher = AES.new(self.key, AES.MODE_CBC, iv)
|
||||||
|
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
|
||||||
|
|
||||||
|
def _pad(self, s):
|
||||||
|
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _unpad(s):
|
||||||
|
return s[:-ord(s[len(s)-1:])]
|