Compare commits

..

36 Commits
0.2 ... 0.2.1

Author SHA1 Message Date
b8ae8cd452 Merge pull request #190 from kevgliss/0.2.1
0.2.1 release info
2015-12-30 09:11:46 -08:00
ca82b227b9 0.2.1 release info 2015-12-30 09:11:19 -08:00
862496495f Merge pull request #189 from m4c3/patch-1
Define ACTIVE_PROVIDERS in default config
2015-12-30 08:50:05 -08:00
8bb9a8c5d1 Define ACTIVE_PROVIDERS in default config
The configuration item ACTIVE_PROVIDERS must be initialized

Workaround for this error:
2015-12-30 13:58:48,073 ERROR: Internal Error [in /www/lemur/local/lib/python2.7/site-packages/flask_restful/__init__.py:299]
Traceback (most recent call last):
  File "/www/lemur/local/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/www/lemur/local/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/www/lemur/local/lib/python2.7/site-packages/flask_restful/__init__.py", line 462, in wrapper
    resp = resource(*args, **kwargs)
  File "/www/lemur/local/lib/python2.7/site-packages/flask/views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "/www/lemur/local/lib/python2.7/site-packages/flask_restful/__init__.py", line 572, in dispatch_request
    resp = meth(*args, **kwargs)
  File "/www/lemur/lemur/auth/views.py", line 276, in get
    for provider in current_app.config.get("ACTIVE_PROVIDERS"):
TypeError: 'NoneType' object is not iterable
2015-12-30 14:56:59 +01:00
00cb66484b Merge pull request #188 from kevgliss/csr
Adding the ability to submit a third party CSR
2015-12-29 12:11:11 -08:00
cabe2ae18d Adding the ability to issue third party created CSRs 2015-12-29 10:49:33 -08:00
665a3f3180 Merge pull request #187 from kevgliss/sso
Fixing some issues with dynamically supporting multiple SSO providers
2015-12-27 22:06:31 -05:00
3b5d7eaab6 More Linting 2015-12-27 18:08:17 -05:00
aa2358aa03 Fixing linting 2015-12-27 18:02:38 -05:00
a7decc1948 Fixing some issues with dynamically supporting multiple SSO providers 2015-12-27 17:54:11 -05:00
38b48604f3 Merge pull request #186 from rpicard/master
Add Google SSO
2015-12-27 15:30:52 -05:00
60856cb7b9 Add an endpoint to return active authentication providers
This endpoint can be used by Angular to figure out what authentication
options to display to the user. It returns a dictionary of configuration
details that the front-end needs for each provider.
2015-12-22 18:03:56 -05:00
350d013043 Add Google SSO
This pull request adds Google SSO support. There are two main changes:

1. Add the Google auth view resource
2. Make passwords optional when creating a new user. This allows an admin
to create a user without a password so that they can only login via Google.
2015-12-22 13:44:30 -05:00
70c92fea15 Merge pull request #183 from kevgliss/rotate
Adding rotate command
2015-12-18 12:05:52 -05:00
6211b126a9 Fixing py3 syntax error 2015-12-18 11:01:08 -05:00
54c3fcc72a Adding rotate command 2015-12-17 23:17:27 -05:00
27c9088ddb Merge pull request #182 from kevgliss/176-p12
Closes #176
2015-12-17 15:01:54 -08:00
b8c2d42cad Closes #176 2015-12-17 14:52:20 -08:00
1f5ddd9530 Merge pull request #181 from kevgliss/172-export
Closes #172
2015-12-17 14:35:55 -08:00
2896ce0dad Closes #172 2015-12-16 08:18:01 -08:00
29bcde145c 0.2.1 release 2015-12-14 10:42:51 -08:00
11db429bcc adding OSSMETADATA for NetflixOSS tracking 2015-12-11 15:57:28 -08:00
75aea9f885 Merge pull request #179 from rpicard/master
Update example supervisor configuration file
2015-12-10 18:31:02 -08:00
c80559005f Update example supervisor configuration file
supervisord should run as root and spawn the lemur process as the lemur
user. I also added the LEMUR_CONF environment variable because it was
not reading the configuration file in by default.
2015-12-10 17:39:49 -08:00
9b927cfcc2 Merge pull request #177 from kevgliss/docs
clarifying upgrade process
2015-12-09 17:29:13 -08:00
4db7931aa0 clarifying upgrade process 2015-12-09 17:18:01 -08:00
1e67329c64 Merge pull request #175 from kevgliss/notifications
Fixing templates
2015-12-04 09:57:42 -08:00
6d17e4d538 Fixing templates 2015-12-04 09:51:38 -08:00
350f58ec9d Merge pull request #174 from kevgliss/binding
Disabling one-time binding
2015-12-03 17:16:19 -08:00
de9478a992 Disabling one-time binding 2015-12-03 16:57:37 -08:00
70a2c985cf Merge pull request #171 from kevgliss/packaging
Fixing the startup port
2015-12-02 17:14:24 -08:00
78037dc9ec Fixing the startup port 2015-12-02 17:13:52 -08:00
9b11efd1e5 Merge pull request #170 from kevgliss/export
Adding export plugin docs
2015-12-02 16:05:36 -08:00
3c2ee8fbb3 Adding export plugin docs 2015-12-02 16:04:40 -08:00
163cc3f795 Merge pull request #169 from kevgliss/bump
Version bump
2015-12-02 14:54:17 -08:00
041382b02f Version bump 2015-12-02 14:53:46 -08:00
28 changed files with 674 additions and 247 deletions

View File

@ -1,13 +1,23 @@
Changelog
=========
0.2.1 - `master` _
0.2.2 - `master` _
~~~~~~~~~~~~~~~~~~
.. note:: This version not yet released and is under active development
0.2.1 - 2015-12-14
~~~~~~~~~~~~~~~~~~
* Fixed bug with search not refreshing values
* Cleaned up documentation, including working supervisor example (thanks rpicard!)
* Closed #165 - Fixed an issue with email templates
* Closed #188 - Added ability to submit third party CSR
* Closed #176 - Java-export should allow user to specify truststore/keystore
* Closed #176 - Extended support for exporting certificate in P12 format
0.2.0 - 2015-12-02
~~~~~~~~~~~~~~~~~~~

1
OSSMETADATA Normal file
View File

@ -0,0 +1 @@
osslifecycle=active

View File

@ -262,11 +262,18 @@ for those plugins.
Authentication
--------------
Lemur currently supports Basic Authentication and Ping OAuth2 out of the box. Additional flows can be added relatively easily.
If you are not using Ping you do not need to configure any of these options.
Lemur currently supports Basic Authentication, Ping OAuth2, and Google out of the box. Additional flows can be added relatively easily.
If you are not using an authentication provider you do not need to configure any of these options.
For more information about how to use social logins, see: `Satellizer <https://github.com/sahat/satellizer>`_
.. data:: ACTIVE_PROVIDERS
:noindex:
::
ACTIVE_PROVIDERS = ["ping", "google"]
.. data:: PING_SECRET
:noindex:
@ -296,6 +303,33 @@ For more information about how to use social logins, see: `Satellizer <https://g
PING_JWKS_URL = "https://<yourpingserver>/pf/JWKS"
.. data:: PING_NAME
:noindex:
::
PING_NAME = "Example Oauth2 Provider"
.. data:: PING_CLIENT_ID
:noindex:
::
PING_CLIENT_ID = "client-id"
.. data:: GOOGLE_CLIENT_ID
:noindex:
::
GOOGLE_CLIENT_ID = "client-id"
.. data:: GOOGLE_SECRET
:noindex:
::
GOOGLE_SECRET = "somethingsecret"
AWS Plugin Configuration
@ -493,6 +527,11 @@ version of Lemur from pypi and then apply any schema changes with the following
.. note:: Internally, this uses `Alembic <https://alembic.readthedocs.org/en/latest/>`_ to manage database migrations.
.. note:: By default Alembic looks for the `migrations` folder in the current working directory.
The migrations folder is located under `<LEMUR_HOME>/lemur/migrations` if you are running the lemur command from any
location besides `<LEMUR_HOME>/lemur` you will need to pass the `-d` flag to specify the absolute file path to the
`migrations` folder.
.. _CommandLineInterface:
Command Line Interface

View File

@ -215,7 +215,7 @@ certificate Lemur does not know about and adding the certificate to it's invento
The `SourcePlugin` object has one default option of `pollRate`. This controls the number of seconds which to get new certificates.
.. warning::
Lemur currently has a very basic polling system of running a cron job every 15min to see which source plugins need to be run. A lock file is generated to guarantee that
Lemur currently has a very basic polling system of running a cron job every 15min to see which source plugins need to be run. A lock file is generated to guarantee that
only one sync is running at a time. It also means that the minimum resolution of a source plugin poll rate is effectively 15min. You can always specify a faster cron
job if you need a higher resolution sync job.
@ -227,7 +227,29 @@ The `SourcePlugin` object requires implementation of one function::
.. Note::
Often times to facilitate code re-use it makes sense put source and destination plugins into one package.
Often times to facilitate code re-use it makes sense put source and destination plugins into one package.
Export
------
Formats, formats and more formats. That's the current PKI landscape. See the always relevant `xkcd <https://xkcd.com/927/>`_.
Thankfully Lemur supports the ability to output your certificates into whatever format you want. This integration comes by the way
of Export plugins. Support is still new and evolving, the goal of these plugins is to return raw data in a new format that
can then be used by any number of applications. Included in Lemur is the `JavaExportPlugin` which currently supports generating
a Java Key Store (JKS) file for use in Java based applications.
The `ExportPlugin` object requires the implementation of one function::
def export(self, body, chain, key, options, **kwargs):
# sys.call('openssl hokuspocus')
# return "extension", passphrase, raw
.. Note::
Support of various formats sometimes relies on external tools system calls. Always be mindful of sanitizing any input to
these calls.
Testing

View File

@ -257,13 +257,12 @@ Create a configuration file named supervisor.ini::
nodaemon=false
minfds=1024
minprocs=200
user=lemur
[program:lemur]
command=python /path/to/lemur/manage.py manage.py start
directory=/path/to/lemur/
environment=PYTHONPATH='/path/to/lemur/'
environment=PYTHONPATH='/path/to/lemur/',LEMUR_CONF='/home/lemur/.lemur/lemur.conf.py'
user=lemur
autostart=true
autorestart=true

View File

@ -158,7 +158,6 @@ Additional notifications can be created through the UI or API. See :ref:`Creati
.. code-block:: bash
$ lemur db init
$ lemur init
.. note:: It is recommended that once the ``lemur`` user is created that you create individual users for every day access. There is currently no way for a user to self enroll for Lemur access, they must have an administrator create an account for them or be enrolled automatically through SSO. This can be done through the CLI or UI. See :ref:`Creating Users <CreatingUsers>` and :ref:`Command Line Interface <CommandLineInterface>` for details.

View File

@ -9,7 +9,7 @@ __title__ = "lemur"
__summary__ = ("Certificate management and orchestration service")
__uri__ = "https://github.com/Netflix/lemur"
__version__ = "0.2"
__version__ = "0.2.1"
__author__ = "The Lemur developers"
__email__ = "security@netflix.com"

View File

@ -230,5 +230,77 @@ class Ping(Resource):
return dict(token=create_token(user))
class Google(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(Google, self).__init__()
def post(self):
access_token_url = 'https://accounts.google.com/o/oauth2/token'
people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'
self.reqparse.add_argument('clientId', type=str, required=True, location='json')
self.reqparse.add_argument('redirectUri', type=str, required=True, location='json')
self.reqparse.add_argument('code', type=str, required=True, location='json')
args = self.reqparse.parse_args()
# Step 1. Exchange authorization code for access token
payload = {
'client_id': args['clientId'],
'grant_type': 'authorization_code',
'redirect_uri': args['redirectUri'],
'code': args['code'],
'client_secret': current_app.config.get('GOOGLE_SECRET')
}
r = requests.post(access_token_url, data=payload)
token = r.json()
# Step 2. Retrieve information about the current user
headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}
r = requests.get(people_api_url, headers=headers)
profile = r.json()
user = user_service.get_by_email(profile['email'])
if user:
return dict(token=create_token(user))
class Providers(Resource):
def get(self):
active_providers = []
for provider in current_app.config.get("ACTIVE_PROVIDERS"):
provider = provider.lower()
if provider == "google":
active_providers.append({
'name': 'google',
'clientId': current_app.config.get("GOOGLE_CLIENT_ID"),
'url': api.url_for(Google)
})
elif provider == "ping":
active_providers.append({
'name': current_app.config.get("PING_NAME"),
'url': current_app.config.get('PING_REDIRECT_URI'),
'redirectUri': current_app.config.get("PING_REDIRECT_URI"),
'clientId': current_app.config.get("PING_CLIENT_ID"),
'responseType': 'code',
'scope': ['openid', 'email', 'profile', 'address'],
'scopeDelimeter': ' ',
'authorizationEndpoint': current_app.config.get("PING_AUTH_ENDPOINT"),
'requiredUrlParams': ['scope'],
'type': '2.0'
})
return active_providers
api.add_resource(Login, '/auth/login', endpoint='login')
api.add_resource(Ping, '/auth/ping', endpoint='ping')
api.add_resource(Google, '/auth/google', endpoint='google')
api.add_resource(Providers, '/auth/providers', endpoint='providers')

View File

@ -140,7 +140,12 @@ def mint(issuer_options):
issuer = plugins.get(authority.plugin_name)
csr, private_key = create_csr(issuer_options)
# allow the CSR to be specified by the user
if not issuer_options.get('csr'):
csr, private_key = create_csr(issuer_options)
else:
csr = issuer_options.get('csr')
private_key = None
issuer_options['creator'] = g.user.email
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)

View File

@ -192,6 +192,7 @@ class CertificatesList(AuthenticatedResource):
"owner": "bob@example.com",
"description": "test",
"selectedAuthority": "timetest2",
"csr",
"authority": {
"body": "-----BEGIN...",
"name": "timetest2",
@ -325,6 +326,7 @@ class CertificatesList(AuthenticatedResource):
self.reqparse.add_argument('organizationalUnit', type=str, location='json', required=True)
self.reqparse.add_argument('owner', type=str, location='json', required=True)
self.reqparse.add_argument('commonName', type=str, location='json', required=True)
self.reqparse.add_argument('csr', type=str, location='json')
args = self.reqparse.parse_args()

View File

@ -90,6 +90,8 @@ LEMUR_DEFAULT_LOCATION = ''
LEMUR_DEFAULT_ORGANIZATION = ''
LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = ''
# Authentication Providers
ACTIVE_PROVIDERS = []
# Logging
@ -503,11 +505,34 @@ def unicode_(data):
return data
class RotateELBs(Command):
"""
Rotates existing certificates to a new one on an ELB
"""
option_list = (
Option('-c', '--cert-name', dest='cert_name', required=True),
Option('-a', '--account-id', dest='account_id', required=True),
Option('-e', '--elb-list', dest='elb_list', required=True)
)
def run(self, cert_name, account_id, elb_list):
from lemur.plugins.lemur_aws import elb
arn = "arn:aws:iam::{0}:server-certificate/{1}".format(account_id, cert_name)
for e in open(elb_list, 'r').readlines():
for region in elb.get_all_regions():
if str(region) in e:
name = "-".join(e.split('.')[0].split('-')[:-1])
if name.startswith("internal"):
name = "-".join(name.split("-")[1:])
elb.update_listeners(account_id, str(region), name, [(443, 7001, 'https', arn)], [443])
sys.out.write("[+] Updated {0} to use {1} on 443\n".format(name, cert_name))
class ProvisionELB(Command):
"""
Creates and provisions a certificate on an ELB based on command line arguments
"""
option_list = (
Option('-d', '--dns', dest='dns', action='append', required=True, type=unicode_),
Option('-e', '--elb', dest='elb_name', required=True, type=unicode_),
@ -746,6 +771,7 @@ def main():
manager.add_command("create_user", CreateUser())
manager.add_command("create_role", CreateRole())
manager.add_command("provision_elb", ProvisionELB())
manager.add_command("rotate_elbs", RotateELBs())
manager.run()
if __name__ == "__main__":

View File

@ -36,13 +36,16 @@ def _get_message_data(cert):
:param cert:
:return:
"""
cert_dict = cert.as_dict()
cert_dict = {}
if cert.user:
cert_dict['creator'] = cert.user.email
cert_dict['domains'] = [x .name for x in cert.domains]
cert_dict['superseded'] = list(set([x.name for x in _find_superseded(cert) if cert.name != x]))
cert_dict['not_after'] = cert.not_after
cert_dict['owner'] = cert.owner
cert_dict['name'] = cert.name
cert_dict['body'] = cert.body
return cert_dict

File diff suppressed because one or more lines are too long

View File

@ -52,6 +52,74 @@ def split_chain(chain):
return certs
def create_truststore(cert, chain, jks_tmp, alias, passphrase):
with mktempfile() as cert_tmp:
with open(cert_tmp, 'w') as f:
f.write(cert)
run_process([
"keytool",
"-importcert",
"-file", cert_tmp,
"-keystore", jks_tmp,
"-alias", "{0}_cert".format(alias),
"-storepass", passphrase,
"-noprompt"
])
# Import the entire chain
for idx, cert in enumerate(split_chain(chain)):
with mktempfile() as c_tmp:
with open(c_tmp, 'w') as f:
f.write(cert)
# Import signed cert in to JKS keystore
run_process([
"keytool",
"-importcert",
"-file", c_tmp,
"-keystore", jks_tmp,
"-alias", "{0}_cert_{1}".format(alias, idx),
"-storepass", passphrase,
"-noprompt"
])
def create_keystore(cert, jks_tmp, key, alias, passphrase):
with mktempfile() as key_tmp:
with open(key_tmp, 'w') as f:
f.write(key)
# Create PKCS12 keystore from private key and public certificate
with mktempfile() as cert_tmp:
with open(cert_tmp, 'w') as f:
f.write(cert)
with mktempfile() as p12_tmp:
run_process([
"openssl",
"pkcs12",
"-export",
"-name", alias,
"-in", cert_tmp,
"-inkey", key_tmp,
"-out", p12_tmp,
"-password", "pass:{}".format(passphrase)
])
# Convert PKCS12 keystore into a JKS keystore
run_process([
"keytool",
"-importkeystore",
"-destkeystore", jks_tmp,
"-srckeystore", p12_tmp,
"-srcstoretype", "PKCS12",
"-alias", alias,
"-srcstorepass", passphrase,
"-deststorepass", passphrase
])
class JavaExportPlugin(ExportPlugin):
title = 'Java'
slug = 'java-export'
@ -66,7 +134,7 @@ class JavaExportPlugin(ExportPlugin):
'name': 'type',
'type': 'select',
'required': True,
'available': ['Java Key Store (JKS)'],
'available': ['Truststore (JKS)', 'Keystore (JKS)'],
'helpMessage': 'Choose the format you wish to export',
},
{
@ -105,71 +173,23 @@ class JavaExportPlugin(ExportPlugin):
else:
alias = "blah"
if not key:
raise Exception("Unable to export, no private key found.")
type = self.get_option('type', options)
with mktempfile() as cert_tmp:
with open(cert_tmp, 'w') as f:
f.write(body)
with mktemppath() as jks_tmp:
if type == 'Truststore (JKS)':
create_truststore(body, chain, jks_tmp, alias, passphrase)
with mktempfile() as key_tmp:
with open(key_tmp, 'w') as f:
f.write(key)
elif type == 'Keystore (JKS)':
if not key:
raise Exception("Unable to export, no private key found.")
# Create PKCS12 keystore from private key and public certificate
with mktempfile() as p12_tmp:
run_process([
"openssl",
"pkcs12",
"-export",
"-name", alias,
"-in", cert_tmp,
"-inkey", key_tmp,
"-out", p12_tmp,
"-password", "pass:{}".format(passphrase)
])
create_truststore(body, chain, jks_tmp, alias, passphrase)
create_keystore(body, jks_tmp, key, alias, passphrase)
# Convert PKCS12 keystore into a JKS keystore
with mktemppath() as jks_tmp:
run_process([
"keytool",
"-importkeystore",
"-destkeystore", jks_tmp,
"-srckeystore", p12_tmp,
"-srcstoretype", "PKCS12",
"-alias", alias,
"-srcstorepass", passphrase,
"-deststorepass", passphrase
])
else:
raise Exception("Unable to export, unsupported type: {0}".format(type))
# Import leaf cert in to JKS keystore
run_process([
"keytool",
"-importcert",
"-file", cert_tmp,
"-keystore", jks_tmp,
"-alias", "{0}_cert".format(alias),
"-storepass", passphrase,
"-noprompt"
])
with open(jks_tmp, 'rb') as f:
raw = f.read()
# Import the entire chain
for idx, cert in enumerate(split_chain(chain)):
with mktempfile() as c_tmp:
with open(c_tmp, 'w') as f:
f.write(cert)
# Import signed cert in to JKS keystore
run_process([
"keytool",
"-importcert",
"-file", c_tmp,
"-keystore", jks_tmp,
"-alias", "{0}_cert_{1}".format(alias, idx),
"-storepass", passphrase,
"-noprompt"
])
with open(jks_tmp, 'rb') as f:
raw = f.read()
return "jks", passphrase, raw
return "jks", passphrase, raw

View File

@ -0,0 +1,5 @@
try:
VERSION = __import__('pkg_resources') \
.get_distribution(__name__).version
except Exception as e:
VERSION = 'unknown'

View File

@ -0,0 +1,130 @@
"""
.. module: lemur.plugins.lemur_openssl.plugin
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import subprocess
from flask import current_app
from lemur.utils import mktempfile, mktemppath
from lemur.plugins.bases import ExportPlugin
from lemur.plugins import lemur_openssl as openssl
from lemur.common.utils import get_psuedo_random_string
def run_process(command):
"""
Runs a given command with pOpen and wraps some
error handling around it.
:param command:
:return:
"""
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
current_app.logger.debug(command)
stdout, stderr = p.communicate()
if p.returncode != 0:
current_app.logger.debug(" ".join(command))
current_app.logger.error(stderr)
raise Exception(stderr)
def create_pkcs12(cert, p12_tmp, key, alias, passphrase):
"""
Creates a pkcs12 formated file.
:param cert:
:param jks_tmp:
:param key:
:param alias:
:param passphrase:
"""
with mktempfile() as key_tmp:
with open(key_tmp, 'w') as f:
f.write(key)
# Create PKCS12 keystore from private key and public certificate
with mktempfile() as cert_tmp:
with open(cert_tmp, 'w') as f:
f.write(cert)
run_process([
"openssl",
"pkcs12",
"-export",
"-name", alias,
"-in", cert_tmp,
"-inkey", key_tmp,
"-out", p12_tmp,
"-password", "pass:{}".format(passphrase)
])
class OpenSSLExportPlugin(ExportPlugin):
title = 'OpenSSL'
slug = 'openssl-export'
description = 'Is a loose interface to openssl and support various formats'
version = openssl.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
options = [
{
'name': 'type',
'type': 'select',
'required': True,
'available': ['PKCS12 (.p12)'],
'helpMessage': 'Choose the format you wish to export',
},
{
'name': 'passphrase',
'type': 'str',
'required': False,
'helpMessage': 'If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8.',
'validation': '^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$'
},
{
'name': 'alias',
'type': 'str',
'required': False,
'helpMessage': 'Enter the alias you wish to use for the keystore.',
}
]
def export(self, body, chain, key, options, **kwargs):
"""
Generates a Java Keystore or Truststore
:param key:
:param chain:
:param body:
:param options:
:param kwargs:
"""
if self.get_option('passphrase', options):
passphrase = self.get_option('passphrase', options)
else:
passphrase = get_psuedo_random_string()
if self.get_option('alias', options):
alias = self.get_option('alias', options)
else:
alias = "blah"
type = self.get_option('type', options)
with mktemppath() as output_tmp:
if type == 'PKCS12 (.p12)':
create_pkcs12(body, output_tmp, key, alias, passphrase)
extension = "p12"
else:
raise Exception("Unable to export, unsupported type: {0}".format(type))
with open(output_tmp, 'rb') as f:
raw = f.read()
return extension, passphrase, raw

View File

@ -0,0 +1 @@
from lemur.tests.conftest import * # noqa

View File

@ -0,0 +1,63 @@
PRIVATE_KEY_STR = b"""
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsXn+QZRATxryRmGXI4fdI+0a2oBwuVh8fC/9bcqX6c5eDmgc
rj6esmc1hpIFxMM3DvkFXX6xISkU6B5fmYDEGZLi7NvcXF3+EoA/SCkP1MFlvqhn
EvNhb0t1fBLs0i/0gfTS/FHBZY1ekHisd/sUetCDZ7F11RxMwws0Oc8bl7j1TpRc
awXFAsh/aWwQOwFeyWU7TtZeAE7sMyWXInBg37tKk1wlv+mN+27WijI091+amkVy
zIV6mA5OHfqbjuqV8uQflN8jE244Qr7shtSk7LpBpWf0M6dC7dXbuUctHFhqcDjy
3IRUl+NisKRoMtq+a0uehfmpFNSUD7F4gdUtSwIDAQABAoIBAGITsZ+aBuPwVzzv
x286MMoeyL1BR4oVzU1v09Rtpf/uLGo3vMnKDzc19A12+rseynl6wi1FyysxIb2Y
s2oID9a2JrOQWLmus66TsuT01CvV6J0xQSzm1MyFXdqANuF84NlEa6hGoeK1+jFK
jr0LQukP+9484oovxnfu5CCiRHRWNZmeuekuYhI1SJf343Tr6jwvyr6KZpnIy0Yt
axuuIZdCfY9ZV2vFG89GwwgwVQrhf14Kv5vBMZrNh1lRGsr0Sqlx5cGkPRAy90lg
HjrRMogrtXr3AR5Pk2qqAYXzZBU2EFhJ3k2njpwOzlSj0r0ZwTmejZ89cco0sW5j
+eQ6aRECgYEA1tkNW75fgwU52Va5VETCzG8II/pZdqNygnoc3z8EutN+1w8f6Tr+
PdpKSICW0z7Iq4f5k/4wrA5xw1vy5RBMH0ZP29GwHTvCPiTBboR9vWvxQvZn1jb9
wvKa0RxE18KcF0YIyTnZMubkA17QTFlvCNyZg0iCqeyFYPyqVE+R4AkCgYEA03h1
XrqECZDDbG9HLUdGbkZNk4VzTcF6dQ3GAPY8M/H7rw5BbvH0RZLOrzl46DDVzKTg
B1VOReAHsxBKFdkqeq1A99CLDow6vHTIEG8DwxkA7/2QPkt8MybwdApUyYnQh5/v
CxwkRt4Mm+EiYfn5iyL8yI+vaQSRToVO/3BND7MCgYAJQSpBJG8qzqPSR9kN1zRo
5/N60ULfSGUbV7U8rJNAlPGmw+EFA+SFt4xxmRBmIxMzyFSo2k8waiLeXmyVD2Go
CzhPaLXkXHmegajPYOelrCulTcXlRVMi/Z5LmaMhhCGDIyInwNUpSybROllQoJ2W
zSHTtODj/usz5U5U+WR4OQKBgHQRosI6t2wUo96peTS18UdnmP7GeZINBuymga5X
eJW+VLkxpuKBNOTW/lCYx+8Rlte7CyebP9oEa9VxtGgniTRKUeVy9lAm0bpMkt7K
QBNebvBKiVhX0DS3Q7U9UmpIFUfLlcXQTW0ERYFtYZTLQpeGvZ5LlyiaFDM34jM7
7WAXAoGANDPJdQLEuimCOAMx/xoecNWeZIP6ieB0hVBrwLNxsaZlkn1KodUMuvla
VEowbtPRdc9o3VZRh4q9cEakssTvOD70hgUZCFcMarmc37RgRvvD2fsZmDZF6qd3
QfHplREs9F0sW+eiirczG7up4XL+CA162TtZxW+2GAiQhwhE5jA=
-----END RSA PRIVATE KEY-----
"""
EXTERNAL_VALID_STR = b"""
-----BEGIN CERTIFICATE-----
MIID2zCCAsOgAwIBAgICA+0wDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT
MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlMb3MgR2F0b3MxDTALBgNV
BAMMBHRlc3QxFjAUBgNVBAoMDU5ldGZsaXgsIEluYy4xEzARBgNVBAsMCk9wZXJh
dGlvbnMxIzAhBgkqhkiG9w0BCQEWFGtnbGlzc29uQG5ldGZsaXguY29tMB4XDTE1
MTEyMzIxNDIxMFoXDTE1MTEyNjIxNDIxMFowcjENMAsGA1UEAwwEdGVzdDEWMBQG
A1UECgwNTmV0ZmxpeCwgSW5jLjETMBEGA1UECwwKT3BlcmF0aW9uczELMAkGA1UE
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALF5/kGUQE8a8kZhlyOH3SPt
GtqAcLlYfHwv/W3Kl+nOXg5oHK4+nrJnNYaSBcTDNw75BV1+sSEpFOgeX5mAxBmS
4uzb3Fxd/hKAP0gpD9TBZb6oZxLzYW9LdXwS7NIv9IH00vxRwWWNXpB4rHf7FHrQ
g2exddUcTMMLNDnPG5e49U6UXGsFxQLIf2lsEDsBXsllO07WXgBO7DMllyJwYN+7
SpNcJb/pjftu1ooyNPdfmppFcsyFepgOTh36m47qlfLkH5TfIxNuOEK+7IbUpOy6
QaVn9DOnQu3V27lHLRxYanA48tyEVJfjYrCkaDLavmtLnoX5qRTUlA+xeIHVLUsC
AwEAAaNVMFMwUQYDVR0fBEowSDBGoESgQoZAaHR0cDovL3Rlc3QuY2xvdWRjYS5j
cmwubmV0ZmxpeC5jb20vdGVzdERlY3JpcHRpb25DQVJvb3QvY3JsLnBlbTANBgkq
hkiG9w0BAQsFAAOCAQEAiHREBKg7zhlQ/N7hDIkxgodRSWD7CVbJGSCdkR3Pvr6+
jHBVNTJUrYqy7sL2pIutoeiSTQEH65/Gbm30mOnNu+lvFKxTxzof6kNYv8cyc8sX
eBuBfSrlTodPFSHXQIpOexZgA0f30LOuXegqzxgXkKg+uMXOez5Zo5pNjTUow0He
oe+V1hfYYvL1rocCmBOkhIGWz7622FxKDawRtZTGVsGsMwMIWyvS3+KQ04K8yHhp
bQOg9zZAoYQuHY1inKBnA0II8eW0hPpJrlZoSqN8Tp0NSBpFiUk3m7KNFP2kITIf
tTneAgyUsgfDxNDifZryZSzg7MH31sTBcYaotSmTXw==
-----END CERTIFICATE-----
"""
def test_export_certificate_to_jks(app):
from lemur.plugins.base import plugins
p = plugins.get('java-export')
options = {'passphrase': 'test1234'}
raw = p.export(EXTERNAL_VALID_STR, "", PRIVATE_KEY_STR, options)
assert raw != b""

View File

@ -1,138 +1,158 @@
'use strict';
var lemur = angular
.module('lemur', [
'ui.router',
'ngTable',
'ngAnimate',
'chart.js',
'restangular',
'angular-loading-bar',
'ui.bootstrap',
'angular-spinkit',
'toaster',
'uiSwitch',
'mgo-angular-wizard',
'satellizer',
'ngLetterAvatar',
'angular-clipboard',
'ngFileSaver'
])
.config(function ($stateProvider, $urlRouterProvider, $authProvider) {
$urlRouterProvider.otherwise('/welcome');
(function() {
var lemur = angular
.module('lemur', [
'ui.router',
'ngTable',
'ngAnimate',
'chart.js',
'restangular',
'angular-loading-bar',
'ui.bootstrap',
'angular-spinkit',
'toaster',
'uiSwitch',
'mgo-angular-wizard',
'satellizer',
'ngLetterAvatar',
'angular-clipboard',
'ngFileSaver'
]);
function fetchData() {
var initInjector = angular.injector(['ng']);
var $http = initInjector.get('$http');
return $http.get('http://localhost:8000/api/1/auth/providers').then(function(response) {
lemur.constant('providers', response.data);
}, function(errorResponse) {
console.log('Could not fetch SSO providers' + errorResponse);
});
}
function bootstrapApplication() {
angular.element(document).ready(function() {
angular.bootstrap(document, ['lemur']);
});
}
fetchData().then(bootstrapApplication);
lemur.config(function ($stateProvider, $urlRouterProvider, $authProvider, providers) {
$urlRouterProvider.otherwise('/welcome');
$stateProvider
.state('welcome', {
url: '/welcome',
templateUrl: 'angular/welcome/welcome.html'
});
$authProvider.oauth2({
name: 'example',
url: 'http://localhost:5000/api/1/auth/ping',
redirectUri: 'http://localhost:3000/',
clientId: 'client-id',
responseType: 'code',
scope: ['openid', 'email', 'profile', 'address'],
scopeDelimiter: ' ',
authorizationEndpoint: 'https://example.com/as/authorization.oauth2',
requiredUrlParams: ['scope']
});
});
lemur.service('MomentService', function () {
this.diffMoment = function (start, end) {
if (end !== 'None') {
return moment(end, 'YYYY-MM-DD HH:mm Z').diff(moment(start, 'YYYY-MM-DD HH:mm Z'), 'minutes') + ' minutes';
}
return 'Unknown';
};
this.createMoment = function (date) {
if (date !== 'None') {
return moment(date, 'YYYY-MM-DD HH:mm Z').fromNow();
}
return 'Unknown';
};
});
lemur.controller('datePickerController', function ($scope, $timeout){
$scope.open = function() {
$timeout(function() {
$scope.opened = true;
});
};
});
lemur.service('DefaultService', function (LemurRestangular) {
var DefaultService = this;
DefaultService.get = function () {
return LemurRestangular.all('defaults').customGET().then(function (defaults) {
return defaults;
});
};
});
lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
return Restangular.withConfig(function (RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://localhost:8000/api/1');
RestangularConfigurer.setDefaultHttpFields({withCredentials: true});
RestangularConfigurer.addResponseInterceptor(function (data, operation) {
var extractedData;
// .. to look for getList operations
if (operation === 'getList') {
// .. and handle the data and meta data
extractedData = data.items;
extractedData.total = data.total;
_.each(providers, function(provider) {
if ($authProvider.hasOwnProperty(provider.name)) {
$authProvider[provider.name] = provider;
} else {
extractedData = data;
}
return extractedData;
});
RestangularConfigurer.setErrorInterceptor(function(response) {
if (response.status === 400) {
if (response.data.message) {
var data = '';
_.each(response.data.message, function (value, key) {
data = data + ' ' + key + ' ' + value;
});
response.data.message = data;
}
$authProvider.oauth2(provider);
}
});
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params) {
// We want to make sure the user is auth'd before any requests
if (!$auth.isAuthenticated()) {
$location.path('/login');
return false;
}
var regExp = /\[([^)]+)\]/;
var s = 'sorting';
var f = 'filter';
var newParams = {};
for (var item in params) {
if (item.indexOf(s) > -1) {
newParams.sortBy = regExp.exec(item)[1];
newParams.sortDir = params[item];
} else if (item.indexOf(f) > -1) {
var key = regExp.exec(item)[1];
newParams.filter = key + ';' + params[item];
} else {
newParams[item] = params[item];
}
}
return { params: newParams };
});
});
});
lemur.run(['$templateCache', function ($templateCache) {
$templateCache.put('ng-table/pager.html', '<div class="ng-cloak ng-table-pager"> <div ng-if="params.settings().counts.length" class="ng-table-counts btn-group pull-left"> <button ng-repeat="count in params.settings().counts" type="button" ng-class="{\'active\':params.count()==count}" ng-click="params.count(count)" class="btn btn-default"> <span ng-bind="count"></span> </button></div><div class="pull-right"><ul style="margin: 0; padding: 0;" class="pagination ng-table-pagination"> <li ng-class="{\'disabled\': !page.active}" ng-repeat="page in pages" ng-switch="page.type"> <a ng-switch-when="prev" ng-click="params.page(page.number)" href="">&laquo;</a> <a ng-switch-when="first" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="page" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="more" ng-click="params.page(page.number)" href="">&#8230;</a> <a ng-switch-when="last" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="next" ng-click="params.page(page.number)" href="">&raquo;</a> </li> </ul> </div></div>');
}]);
lemur.service('MomentService', function () {
this.diffMoment = function (start, end) {
if (end !== 'None') {
return moment(end, 'YYYY-MM-DD HH:mm Z').diff(moment(start, 'YYYY-MM-DD HH:mm Z'), 'minutes') + ' minutes';
}
return 'Unknown';
};
this.createMoment = function (date) {
if (date !== 'None') {
return moment(date, 'YYYY-MM-DD HH:mm Z').fromNow();
}
return 'Unknown';
};
});
lemur.controller('datePickerController', function ($scope, $timeout){
$scope.open = function() {
$timeout(function() {
$scope.opened = true;
});
};
});
lemur.service('DefaultService', function (LemurRestangular) {
var DefaultService = this;
DefaultService.get = function () {
return LemurRestangular.all('defaults').customGET().then(function (defaults) {
return defaults;
});
};
});
lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
return Restangular.withConfig(function (RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://localhost:8000/api/1');
RestangularConfigurer.setDefaultHttpFields({withCredentials: true});
RestangularConfigurer.addResponseInterceptor(function (data, operation) {
var extractedData;
// .. to look for getList operations
if (operation === 'getList') {
// .. and handle the data and meta data
extractedData = data.items;
extractedData.total = data.total;
} else {
extractedData = data;
}
return extractedData;
});
RestangularConfigurer.setErrorInterceptor(function(response) {
if (response.status === 400) {
if (response.data.message) {
var data = '';
_.each(response.data.message, function (value, key) {
data = data + ' ' + key + ' ' + value;
});
response.data.message = data;
}
}
});
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params) {
// We want to make sure the user is auth'd before any requests
if (!$auth.isAuthenticated()) {
$location.path('/login');
return false;
}
var regExp = /\[([^)]+)\]/;
var s = 'sorting';
var f = 'filter';
var newParams = {};
for (var item in params) {
if (item.indexOf(s) > -1) {
newParams.sortBy = regExp.exec(item)[1];
newParams.sortDir = params[item];
} else if (item.indexOf(f) > -1) {
var key = regExp.exec(item)[1];
newParams.filter = key + ';' + params[item];
} else {
newParams[item] = params[item];
}
}
return { params: newParams };
});
});
});
lemur.run(['$templateCache', function ($templateCache) {
$templateCache.put('ng-table/pager.html', '<div class="ng-cloak ng-table-pager"> <div ng-if="params.settings().counts.length" class="ng-table-counts btn-group pull-left"> <button ng-repeat="count in params.settings().counts" type="button" ng-class="{\'active\':params.count()==count}" ng-click="params.count(count)" class="btn btn-default"> <span ng-bind="count"></span> </button></div><div class="pull-right"><ul style="margin: 0; padding: 0;" class="pagination ng-table-pagination"> <li ng-class="{\'disabled\': !page.active}" ng-repeat="page in pages" ng-switch="page.type"> <a ng-switch-when="prev" ng-click="params.page(page.number)" href="">&laquo;</a> <a ng-switch-when="first" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="page" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="more" ng-click="params.page(page.number)" href="">&#8230;</a> <a ng-switch-when="last" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="next" ng-click="params.page(page.number)" href="">&raquo;</a> </li> </ul> </div></div>');
}]);
}());

View File

@ -8,11 +8,13 @@ angular.module('lemur')
controller: 'LoginController'
});
})
.controller('LoginController', function ($rootScope, $scope, AuthenticationService, UserService) {
.controller('LoginController', function ($rootScope, $scope, AuthenticationService, UserService, providers) {
$scope.login = AuthenticationService.login;
$scope.authenticate = AuthenticationService.authenticate;
$scope.logout = AuthenticationService.logout;
$scope.providers = providers;
UserService.getCurrentUser().then(function (user) {
$scope.currentUser = user;
});

View File

@ -3,8 +3,8 @@
<div class="login">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<button class="btn btn-block btn-default" ng-click="authenticate('Example')">
Login with Example
<button class="btn btn-block btn-default" ng-repeat="(key, value) in providers" ng-click="authenticate(value.name)">
Login with {{ value.name }}
</button>
</div>
</div>

View File

@ -48,7 +48,7 @@
</div>
</div>
</div>
<div ng-show="certificate.authority" class="form-group">
<div class="form-group">
<label class="control-label col-sm-2">
Certificate Template
</label>
@ -110,6 +110,20 @@
</div>
</div>
</div>
<div class="form-group"
ng-class="{'has-error': trackingForm.csr.$invalid&&trackingForm.csr.$dirty, 'has-success': !trackingForm.csr.$invalid&&trackingForm.csr.$dirty}">
<label class="control-label col-sm-2">
Certificate Signing Request (CSR)
</label>
<div class="col-sm-10">
<textarea tooltip="Values defined in the CSR will take precedence" name="certificate signing request" ng-model="certificate.csr"
placeholder="PEM encoded string..." class="form-control"
ng-pattern="/^-----BEGIN CERTIFICATE REQUEST-----/"></textarea>
<p ng-show="trackingForm.csr.$invalid && !trackingForm.csr.$pristine"
class="help-block">Enter a valid certificate signing request.</p>
</div>
</div>
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>

View File

@ -24,8 +24,8 @@
<tr ng-class="{'even-row': $even }" ng-repeat-start="certificate in $data track by $index">
<td data-title="'Name'" sortable="'name'" filter="{ 'name': 'text' }">
<ul class="list-unstyled">
<li>{{ ::certificate.name }}</li>
<li><span class="text-muted">{{ ::certificate.owner }}</span></li>
<li>{{ certificate.name }}</li>
<li><span class="text-muted">{{ certificate.owner }}</span></li>
</ul>
</td>
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getCertificateStatus()">
@ -35,10 +35,10 @@
</form>
</td>
<td data-title="'Issuer'" sortable="'issuer'" filter="{ 'issuer': 'text' }">
{{ ::certificate.authority.name || certificate.issuer }}
{{ certificate.authority.name || certificate.issuer }}
</td>
<td data-title="'Domains'" filter="{ 'cn': 'text'}">
{{ ::certificate.cn }}
{{ certificate.cn }}
</td>
<td class="col-md-2" data-title="''">
<div class="btn-group pull-right">
@ -62,19 +62,19 @@
<li class="list-group-item">
<strong>Creator</strong>
<span class="pull-right">
{{ ::certificate.creator.email }}
{{ certificate.creator.email }}
</span>
</li>
<li class="list-group-item">
<strong>Not Before</strong>
<span class="pull-right" tooltip="{{ ::certificate.notBefore }}">
{{ ::momentService.createMoment(certificate.notBefore) }}
<span class="pull-right" tooltip="{{ certificate.notBefore }}">
{{ momentService.createMoment(certificate.notBefore) }}
</span>
</li>
<li class="list-group-item">
<strong>Not After</strong>
<span class="pull-right" tooltip="{{ ::certificate.notAfter }}">
{{ ::momentService.createMoment(certificate.notAfter) }}
<span class="pull-right" tooltip="{{ certificate.notAfter }}">
{{ momentService.createMoment(certificate.notAfter) }}
</span>
</li>
<li class="list-group-item">
@ -86,15 +86,15 @@
</li>
<li class="list-group-item">
<strong>Bits</strong>
<span class="pull-right">{{ ::certificate.bits }}</span>
<span class="pull-right">{{ certificate.bits }}</span>
</li>
<li class="list-group-item">
<strong>Signing Algorithm</strong>
<span class="pull-right">{{ ::certificate.signingAlgorithm }}</span>
<span class="pull-right">{{ certificate.signingAlgorithm }}</span>
</li>
<li class="list-group-item">
<strong>Serial</strong>
<span class="pull-right">{{ ::certificate.serial }}</span>
<span class="pull-right">{{ certificate.serial }}</span>
</li>
<li
tooltip="Lemur will attempt to check a certificates validity, this is used to track whether a certificate as been revoked"
@ -108,7 +108,7 @@
</li>
<li class="list-group-item">
<strong>Description</strong>
<p>{{ ::certificate.description }}</p>
<p>{{ certificate.description }}</p>
</li>
</ul>
</tab>
@ -116,8 +116,8 @@
<tab-heading>Notifications</tab-heading>
<ul class="list-group">
<li class="list-group-item" ng-repeat="notification in certificate.notifications">
<strong>{{ ::notification.label }}</strong>
<span class="pull-right">{{ ::notification.description}}</span>
<strong>{{ notification.label }}</strong>
<span class="pull-right">{{ notification.description}}</span>
</li>
</ul>
</tab>
@ -125,24 +125,24 @@
<tab-heading>Destinations</tab-heading>
<ul class="list-group">
<li class="list-group-item" ng-repeat="destination in certificate.destinations">
<strong>{{ ::destination.label }}</strong>
<span class="pull-right">{{ ::destination.description }}</span>
<strong>{{ destination.label }}</strong>
<span class="pull-right">{{ destination.description }}</span>
</li>
</ul>
</tab>
<tab>
<tab-heading>Domains</tab-heading>
<div class="list-group">
<a href="#/domains/{{ ::domain.id }}" class="list-group-item"
ng-repeat="domain in certificate.domains">{{ ::domain.name }}</a>
<a href="#/domains/{{ domain.id }}" class="list-group-item"
ng-repeat="domain in certificate.domains">{{ domain.name }}</a>
</div>
</tab>
<tab>
<tab-heading>Replaces</tab-heading>
<ul class="list-group">
<li class="list-group-item" ng-repeat="replacement in certificate.replacements">
<strong>{{ ::replacement.name }}</strong>
<p>{{ ::replacement.description}}</p>
<strong>{{ replacement.name }}</strong>
<p>{{ replacement.description}}</p>
</li>
</ul>
</tab>
@ -155,7 +155,7 @@
tooltip="Copy chain to clipboard" tooltip-trigger="mouseenter" clipboard
text="certificate.chain"></button>
</tab-heading>
<pre style="width: 100%">{{ ::certificate.chain }}</pre>
<pre style="width: 100%">{{ certificate.chain }}</pre>
</tab>
<tab>
<tab-heading>
@ -164,7 +164,7 @@
tooltip="Copy certificate to clipboard" tooltip-trigger="mouseenter" clipboard
text="certificate.body"></button>
</tab-heading>
<pre style="width: 100%">{{ ::certificate.body }}</pre>
<pre style="width: 100%">{{ certificate.body }}</pre>
</tab>
<tab ng-click="loadPrivateKey(certificate)">
<tab-heading>
@ -173,7 +173,7 @@
tooltip="Copy key to clipboard" tooltip-trigger="mouseenter" clipboard
text="certificate.privateKey"></button>
</tab-heading>
<pre style="width: 100%">{{ ::certificate.privateKey }}</pre>
<pre style="width: 100%">{{ certificate.privateKey }}</pre>
</tab>
</tabset>
</td>

View File

@ -30,8 +30,7 @@
Password
</label>
<div class="col-sm-10">
<input type="password" name="password" ng-model="user.password" placeholder="hunter2" class="form-control" required/>
<p ng-show="createForm.password.$invalid && !createForm.password.$pristine" class="help-block">You must enter an password</p>
<input type="password" name="password" ng-model="user.password" placeholder="hunter2" class="form-control" />
</div>
</div>
<div class="form-group">

View File

@ -32,7 +32,7 @@
<!-- endbuild -->
</head>
<body ng-app="lemur" ng-csp>
<body ng-csp>
<toaster-container></toaster-container>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>

View File

@ -52,7 +52,8 @@ class User(db.Model):
:param password:
:return:
"""
return bcrypt.check_password_hash(self.password, password)
if self.password:
return bcrypt.check_password_hash(self.password, password)
def hash_password(self):
"""
@ -60,8 +61,9 @@ class User(db.Model):
:return:
"""
self.password = bcrypt.generate_password_hash(self.password)
return self.password
if self.password:
self.password = bcrypt.generate_password_hash(self.password)
return self.password
@property
def is_admin(self):

View File

@ -157,7 +157,7 @@ class UsersList(AuthenticatedResource):
"""
self.reqparse.add_argument('username', type=str, location='json', required=True)
self.reqparse.add_argument('email', type=str, location='json', required=True)
self.reqparse.add_argument('password', type=str, location='json', required=True)
self.reqparse.add_argument('password', type=str, location='json', default=None)
self.reqparse.add_argument('active', type=bool, default=True, location='json')
self.reqparse.add_argument('roles', type=roles, default=[], location='json')

View File

@ -74,6 +74,8 @@ docs_require = [
dev_requires = [
'flake8>=2.0,<3.0',
'invoke',
'twine'
]
@ -164,6 +166,7 @@ setup(
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin',
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin',
'java_export = lemur.plugins.lemur_java.plugin:JavaExportPlugin'
'openssl_export = lemur.plugins.lemur_openssl.plugin:OpenSSLExportPlugin'
],
},
classifiers=[