Merge pull request #24 from kevgliss/ci

Adding Travis CI Support
This commit is contained in:
kevgliss 2015-07-23 13:05:35 -07:00
commit c04e23d3ea
103 changed files with 694 additions and 568 deletions

2
.gitignore vendored
View File

@ -26,3 +26,5 @@ pip-log.txt
docs/_build
.editorconfig
.idea
test.conf
lemur/tests/tmp

View File

@ -1,4 +1,2 @@
tests/
lemur/static/lemur/scripts/lib/
lemur/static/lemur/dist/
lemur/static/lemur/vendor/
lemur/static//dist/
lemur/static/app/vendor/

View File

@ -3,13 +3,13 @@
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"camelcase": false,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"newcap": false,
"noarg": true,
"quotmark": "single",
"regexp": true,
@ -19,6 +19,9 @@
"trailing": true,
"smarttabs": true,
"globals": {
"angular": false
"angular": false,
"moment": false,
"toaster": false,
"_": false
}
}

View File

@ -1,7 +1,26 @@
language: node_js
node_js:
- '0.8'
- '0.10'
sudo: false
language: python
addons:
postgresql: "9.4"
python:
- "2.7"
cache:
directories:
- node_modules
- .pip_download_cache
- "$HOME/virtualenv/python2.7.9"
env:
global:
- PIP_DOWNLOAD_CACHE=".pip_download_cache"
install:
- make dev-postgres
before_script:
- 'npm install -g bower grunt-cli'
- 'bower install'
- psql -c "create database lemur;" -U postgres
- psql -c "create user lemur with password 'lemur;'" -U postgres
- npm install -g bower
script:
- make test
notifications:
email:
kglisson@netflix.com

85
Makefile Normal file
View File

@ -0,0 +1,85 @@
NPM_ROOT = ./node_modules
STATIC_DIR = src/lemur/static/app
develop: update-submodules setup-git
@echo "--> Installing dependencies"
npm install
pip install "setuptools>=0.9.8"
# order matters here, base package must install first
# this is temporary until the version we need is released
pip install -e 'git+https://git@github.com/pyca/cryptography.git#egg=cryptography-1.0.dev1'
pip install -e .
pip install "file://`pwd`#egg=lemur[dev]"
pip install "file://`pwd`#egg=lemur[tests]"
@echo ""
dev-docs:
pip install -r docs/requirements.txt
reset-db:
@echo "--> Dropping existing 'lemur' database"
dropdb lemur || true
@echo "--> Creating 'lemur' database"
createdb -E utf-8 lemur
@echo "--> Applying migrations"
lemur db upgrade
setup-git:
@echo "--> Installing git hooks"
git config branch.autosetuprebase always
cd .git/hooks && ln -sf ../../hooks/* ./
@echo ""
clean:
@echo "--> Cleaning static cache"
${NPM_ROOT}/.bin/gulp clean
@echo "--> Cleaning pyc files"
find . -name "*.pyc" -delete
@echo ""
test: develop lint test-python
testloop: develop
pip install pytest-xdist
py.test tests -f
test-cli:
@echo "--> Testing CLI"
rm -rf test_cli
mkdir test_cli
cd test_cli && lemur create_config -c ./test.conf > /dev/null
cd test_cli && lemur -c ./test.conf db upgrade > /dev/null
cd test_cli && lemur -c ./test.conf help 2>&1 | grep start > /dev/null
rm -r test_cli
@echo ""
test-js:
@echo "--> Running JavaScript tests"
npm test
@echo ""
test-python:
@echo "--> Running Python tests"
py.test lemur/tests || exit 1
@echo ""
lint: lint-python lint-js
lint-python:
@echo "--> Linting Python files"
PYFLAKES_NODOCTEST=1 flake8 lemur
@echo ""
lint-js:
@echo "--> Linting JavaScript files"
npm run lint
@echo ""
coverage: develop
coverage run --source=lemur -m py.test
coverage html
publish:
python setup.py sdist bdist_wheel upload
.PHONY: develop dev-postgres dev-docs setup-git build clean update-submodules test testloop test-cli test-js test-python lint lint-python lint-js coverage publish

5
docs/requirements.txt Normal file
View File

@ -0,0 +1,5 @@
Jinja2>=2.3
Pygments>=1.2
Sphinx>=1.3
docutils>=0.7
markupsafe

View File

@ -26,9 +26,9 @@ var gulp = require('gulp'),
imagemin = require('gulp-imagemin'),
minifyHtml = require('gulp-minify-html'),
bowerFiles = require('main-bower-files'),
karma = require('karma'),
replace = require('gulp-replace');
gulp.task('default', ['clean'], function () {
gulp.start('fonts', 'styles');
});
@ -37,6 +37,15 @@ gulp.task('clean', function (cb) {
del(['.tmp', 'lemur/static/dist'], cb);
});
gulp.task('test', function (done) {
new karma.Server({
configFile: __dirname + '/karma.conf.js',
singleRun: true
}, function() {
done();
}).start();
});
gulp.task('dev:fonts', function () {
var fileList = [
'lemur/static/app/vendor/bower_components/bootstrap/dist/fonts/*',

27
gulp/karma.conf.js Normal file
View File

@ -0,0 +1,27 @@
// Contents of: config/karma.conf.js
module.exports = function (config) {
config.set({
basePath : '../',
// Fix for "JASMINE is not supported anymore" warning
frameworks : ["jasmine"],
files : [
'app/lib/angular/angular.js',
'app/lib/angular/angular-*.js',
'test/lib/angular/angular-mocks.js',
'app/js/**/*.js',
'test/unit/**/*.js'
],
autoWatch : true,
browsers : ['Chrome'],
junitReporter : {
outputFile : 'test_out/unit.xml',
suite : 'unit'
//...
}
});
};

View File

@ -27,5 +27,5 @@ gulp.task('default', function () {
console.log(c.green + '-------------------------------------------' + c.reset);
console.log(Object.keys(gulp.tasks).sort().join('\n'));
console.log('');
return;
});

10
hooks/pre-commit Normal file → Executable file
View File

@ -29,14 +29,6 @@ def py_lint(files_modified):
return report.total_errors != 0
def js_lint(files_modified):
has_errors = False
if os.system('node_modules/.bin/jshint src/sentry'):
has_errors = True
return has_errors
def main():
from flake8.hooks import run
@ -46,7 +38,7 @@ def main():
files_modified = filter(lambda x: os.path.exists(x), files_modified)
if any((py_lint(files_modified), js_lint(files_modified))):
if py_lint(files_modified):
return 1
return 0

View File

@ -36,6 +36,7 @@ LEMUR_BLUEPRINTS = (
plugins_bp,
)
def create_app(config=None):
app = factory.create_app(app_name=__name__, blueprints=LEMUR_BLUEPRINTS, config=config)
configure_hook(app)
@ -61,4 +62,3 @@ def configure_hook(app):
response = {'message': 'You are not allow to access this resource'}
response.status_code = 403
return response

View File

@ -37,7 +37,9 @@
# log.debug(e)
# if 'hostname' in str(e):
# tests.append('pass')
# result['details'].append("{}: This test passed ssl negotiation but failed hostname verification becuase the hostname is not included in the certificate".format(region))
# result['details'].append(
# "{}: This test passed ssl negotiation but failed hostname verification because \
# the hostname is not included in the certificate".format(region))
# elif 'certificate verify failed' in str(e):
# tests.append('fail')
# result['details'].append("{}: This test failed to verify the SSL certificate".format(region))

View File

@ -28,7 +28,7 @@ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from lemur.users import service as user_service
from lemur.auth.permissions import CertificateOwnerNeed, CertificateCreatorNeed, \
AuthorityCreatorNeed, AuthorityOwnerNeed, ViewRoleCredentialsNeed
AuthorityCreatorNeed, ViewRoleCredentialsNeed
def base64url_decode(data):
@ -143,7 +143,6 @@ def fetch_token_header(token):
raise jwt.DecodeError('Invalid header padding')
@identity_loaded.connect
def on_identity_loaded(sender, identity):
"""
@ -187,5 +186,3 @@ class AuthenticatedResource(Resource):
def __init__(self):
super(AuthenticatedResource, self).__init__()

View File

@ -9,16 +9,16 @@ import jwt
import base64
import requests
from flask import g, Blueprint, current_app, abort
from flask import g, Blueprint, current_app
from flask.ext.restful import reqparse, Resource, Api
from flask.ext.principal import Identity, identity_changed
from lemur.auth.permissions import admin_permission
from lemur.common.utils import get_psuedo_random_string
from lemur.users import service as user_service
from lemur.roles import service as role_service
from lemur.certificates import service as cert_service
from lemur.auth.service import AuthenticatedResource, create_token, fetch_token_header, get_rsa_public_key
from lemur.auth.service import create_token, fetch_token_header, get_rsa_public_key
mod = Blueprint('auth', __name__)
@ -203,7 +203,7 @@ class Ping(Resource):
user = user_service.create(
profile['email'],
cert_service.create_challenge(),
get_psuedo_random_string(),
profile['email'],
True,
profile.get('thumbnailPhotoUrl'),
@ -222,7 +222,7 @@ class Ping(Resource):
profile['email'],
profile['email'],
True,
profile.get('thumbnailPhotoUrl'), # incase profile isn't google+ enabled
profile.get('thumbnailPhotoUrl'), # Encase profile isn't google+ enabled
roles
)
@ -234,5 +234,3 @@ class Ping(Resource):
api.add_resource(Login, '/auth/login', endpoint='login')
api.add_resource(Ping, '/auth/ping', endpoint='ping')

View File

@ -19,6 +19,7 @@ import lemur.certificates.service as cert_service
from lemur.plugins.base import plugins
def update(authority_id, active=None, roles=None):
"""
Update a an authority with new values.

View File

@ -53,6 +53,7 @@ class UnableToCreateCSR(LemurException):
def __str__(self):
return repr(self.data['message'])
class UnableToCreatePrivateKey(LemurException):
def __init__(self):
self.code = 500
@ -63,6 +64,7 @@ class UnableToCreatePrivateKey(LemurException):
def __str__(self):
return repr(self.data['message'])
class MissingFiles(LemurException):
def __init__(self, path):
self.code = 500
@ -84,4 +86,3 @@ class NoPersistanceFound(LemurException):
def __str__(self):
return repr(self.data['message'])

View File

@ -21,7 +21,7 @@ from lemur.database import db
from lemur.domains.models import Domain
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE, NONSTANDARD_NAMING_TEMPLATE
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE
from lemur.models import certificate_associations, certificate_destination_associations
@ -51,6 +51,7 @@ def create_name(issuer, not_before, not_after, subject, san):
# aws doesn't allow special chars except '-'
disallowed_chars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())
disallowed_chars = disallowed_chars.replace("-", "")
disallowed_chars = disallowed_chars.replace(".", "")
temp = temp.replace('*', "WILDCARD")
temp = temp.translate(None, disallowed_chars)
# white space is silly too
@ -76,7 +77,7 @@ def cert_get_domains(cert):
return the common name.
:param cert:
:return: List of domainss
:return: List of domains
"""
domains = []
try:
@ -86,6 +87,7 @@ def cert_get_domains(cert):
domains.append(entry)
except Exception as e:
current_app.logger.warning("Failed to get SubjectAltName: {0}".format(e))
return domains
@ -110,6 +112,7 @@ def cert_is_san(cert):
if len(cert_get_domains(cert)) > 1:
return True
def cert_is_wildcard(cert):
"""
Determines if certificate is a wildcard certificate.
@ -121,6 +124,9 @@ def cert_is_wildcard(cert):
if len(domains) == 1 and domains[0][0:1] == "*":
return True
if cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value[0:1] == "*":
return True
def cert_get_bitstrength(cert):
"""
@ -266,4 +272,3 @@ class Certificate(db.Model):
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

View File

@ -6,8 +6,6 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import arrow
import string
import random
from sqlalchemy import func, or_
from flask import g, current_app
@ -27,7 +25,6 @@ from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
def get(cert_id):
"""
Retrieves certificate by it's ID.
@ -106,7 +103,6 @@ def mint(issuer_options):
csr, private_key = create_csr(issuer_options)
issuer_options['challenge'] = create_challenge()
issuer_options['creator'] = g.user.email
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
@ -212,8 +208,8 @@ def render(args):
time_range = args.pop('time_range')
destination_id = args.pop('destination_id')
show = args.pop('show')
owner = args.pop('owner')
creator = args.pop('creator') # TODO we should enabling filtering by owner
# owner = args.pop('owner')
# creator = args.pop('creator') # TODO we should enabling filtering by owner
filt = args.pop('filter')
@ -354,16 +350,6 @@ def create_csr(csr_config):
return csr, pem
def create_challenge():
"""
Create a random and strongish csr challenge.
"""
challenge = ''.join(random.choice(string.ascii_uppercase) for x in range(6))
challenge += ''.join(random.choice("~!@#$%^&*()_+") for x in range(6))
challenge += ''.join(random.choice(string.ascii_lowercase) for x in range(6))
challenge += ''.join(random.choice(string.digits) for x in range(6))
return challenge
def stats(**kwargs):
"""
@ -405,5 +391,3 @@ def stats(**kwargs):
values.append(count)
return {'labels': keys, 'values': values}

View File

@ -21,6 +21,7 @@ from lemur.certificates import service as cert_service
from lemur.plugins.base import plugins
from lemur.plugins.bases.source import SourcePlugin
def sync():
for plugin in plugins:
new = 0
@ -42,5 +43,4 @@ def sync():
# TODO associated cert with source
# TODO update cert if found from different source
# TODO dissassociate source if missing
# TODO disassociate source if missing

View File

@ -51,7 +51,7 @@ def valid_authority(authority_options):
"""
Defends against invalid authorities
:param authority_name:
:param authority_options:
:return: :raise ValueError:
"""
name = authority_options['name']
@ -76,7 +76,7 @@ def pem_str(value, name):
"""
try:
x509.load_pem_x509_certificate(str(value), default_backend())
except Exception as e:
except Exception:
raise ValueError("The parameter '{0}' needs to be a valid PEM string".format(name))
return value
@ -91,12 +91,11 @@ def private_key_str(value, name):
"""
try:
serialization.load_pem_private_key(str(value), None, backend=default_backend())
except Exception as e:
except Exception:
raise ValueError("The parameter '{0}' needs to be a valid RSA private key".format(name))
return value
class CertificatesList(AuthenticatedResource):
""" Defines the 'certificates' endpoint """
def __init__(self):
@ -274,8 +273,8 @@ class CertificatesList(AuthenticatedResource):
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
self.reqparse.add_argument('elbs', type=list, location='json')
self.reqparse.add_argument('owner', type=str, location='json')
self.reqparse.add_argument('validityStart', type=str, location='json') # parse date
self.reqparse.add_argument('validityEnd', type=str, location='json') # parse date
self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate
self.reqparse.add_argument('validityEnd', type=str, location='json') # TODO validate
self.reqparse.add_argument('authority', type=valid_authority, location='json')
self.reqparse.add_argument('description', type=str, location='json')
self.reqparse.add_argument('country', type=str, location='json')

View File

@ -10,6 +10,7 @@ from flask import Blueprint
mod = Blueprint('healthCheck', __name__)
@mod.route('/healthcheck')
def health():
return 'ok'

View File

@ -8,6 +8,7 @@
"""
from flask import current_app
# inspired by https://github.com/getsentry/sentry
class InstanceManager(object):
def __init__(self, class_list=None, instances=True):

View File

@ -6,8 +6,8 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import current_app
import boto.ses
from flask import current_app
from lemur.templates.config import env
@ -26,4 +26,3 @@ def send(subject, data, email_type, recipients):
template = env.get_template('{}.html'.format(email_type))
body = template.render(**data)
conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, recipients, format='html')

View File

@ -6,16 +6,28 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import string
import random
from functools import wraps
from flask import current_app
from flask.ext.restful import marshal
from flask.ext.restful.reqparse import RequestParser
from flask.ext.sqlalchemy import Pagination
def get_psuedo_random_string():
"""
Create a random and strongish challenge.
"""
challenge = ''.join(random.choice(string.ascii_uppercase) for x in range(6)) # noqa
challenge += ''.join(random.choice("~!@#$%^&*()_+") for x in range(6)) # noqa
challenge += ''.join(random.choice(string.ascii_lowercase) for x in range(6))
challenge += ''.join(random.choice(string.digits) for x in range(6)) # noqa
return challenge
class marshal_items(object):
def __init__(self, fields, envelope=None):
self.fields = fields

View File

@ -6,5 +6,3 @@
SAN_NAMING_TEMPLATE = "SAN-{subject}-{issuer}-{not_before}-{not_after}"
DEFAULT_NAMING_TEMPLATE = "{subject}-{issuer}-{not_before}-{not_after}"
NONSTANDARD_NAMING_TEMPLATE = "{issuer}-{not_before}-{not_after}"

View File

@ -9,13 +9,11 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import current_app
from sqlalchemy import exc
from sqlalchemy.sql import and_, or_
from lemur.extensions import db
from lemur.exceptions import AttrNotFound, IntegrityError, DuplicateError
from lemur.exceptions import AttrNotFound, DuplicateError
def filter_none(kwargs):
@ -126,7 +124,7 @@ def get(model, value, field="id"):
query = session_query(model)
try:
return query.filter(getattr(model, field) == value).one()
except:
except Exception:
return
@ -178,6 +176,7 @@ def delete(model):
:param model:
"""
if model:
db.session.delete(model)
db.session.commit()
@ -209,7 +208,7 @@ def sort(query, model, field, direction):
direction = getattr(field, direction)
query = query.order_by(direction())
return query
except AttributeError as e:
except AttributeError:
raise AttrNotFound(field)
@ -274,6 +273,3 @@ def sort_and_page(query, model, args):
query = sort(query, model, sort_by, sort_dir)
return paginate(query, page, count)

View File

@ -9,9 +9,10 @@ from flask import make_response, request, current_app
from functools import update_wrapper
# this is only used for dev
def crossdomain(origin=None, methods=None, headers=None,
max_age=21600, attach_to_all=True,
automatic_options=True):
automatic_options=True): # pragma: no cover
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
@ -44,12 +45,10 @@ def crossdomain(origin=None, methods=None, headers=None,
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
#if headers is not None:
h['Access-Control-Allow-Headers'] = "Origin, X-Requested-With, Content-Type, Accept, Authorization " # headers
h['Access-Control-Allow-Headers'] = "Origin, X-Requested-With, Content-Type, Accept, Authorization "
h['Access-Control-Allow-Credentials'] = 'true'
return resp
f.provide_automatic_options = False
return update_wrapper(wrapped_function, f)
return decorator

View File

@ -7,18 +7,13 @@ ADMINS = frozenset([''])
THREADS_PER_PAGE = 8
#############
## General ##
#############
# General
# These will need to be set to `True` if you are developing locally
CORS = False
debug = False
#############
## Logging ##
#############
# Logging
LOG_LEVEL = "DEBUG"
LOG_FILE = "lemur.log"

View File

@ -12,6 +12,7 @@ from lemur.database import db
from lemur.plugins.base import plugins
class Destination(db.Model):
__tablename__ = 'destinations'
id = Column(Integer, primary_key=True)

View File

@ -37,7 +37,7 @@ def update(destination_id, label, options, description):
destination = get(destination_id)
destination.label = label
description.options = options
destination.options = options
destination.description = description
return database.update(destination)
@ -107,4 +107,3 @@ def render(args):
query = database.sort(query, Destination, sort_by, sort_dir)
return database.paginate(query, page, count)

View File

@ -229,7 +229,6 @@ class Destinations(AuthenticatedResource):
return {'result': True}
class CertificateDestinations(AuthenticatedResource):
""" Defines the 'certificate/<int:certificate_id/destinations'' endpoint """
def __init__(self):
@ -274,5 +273,5 @@ class CertificateDestinations(AuthenticatedResource):
api.add_resource(DestinationsList, '/destinations', endpoint='destinations')
api.add_resource(Destinations, '/destinations/<int:destination_id>', endpoint='account')
api.add_resource(CertificateDestinations, '/certificates/<int:certificate_id>/destinations', endpoint='certificateDestinations')
api.add_resource(CertificateDestinations, '/certificates/<int:certificate_id>/destinations',
endpoint='certificateDestinations')

View File

@ -24,4 +24,3 @@ class Domain(db.Model):
blob = self.as_dict()
blob['certificates'] = [x.id for x in self.certificate]
return blob

View File

@ -61,4 +61,3 @@ def render(args):
query = database.sort(query, Domain, sort_by, sort_dir)
return database.paginate(query, page, count)

View File

@ -6,7 +6,7 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from sqlalchemy import Column, BigInteger, String, ForeignKey, DateTime, PassiveDefault, func
from sqlalchemy import Column, BigInteger, String, DateTime, PassiveDefault, func
from sqlalchemy.orm import relationship
from lemur.database import db

View File

@ -14,6 +14,7 @@ from lemur import database
from lemur.elbs.models import ELB
from lemur.listeners.models import Listener
def get_all(account_id, elb_name):
"""
Retrieves all ELBs in a given account
@ -112,7 +113,7 @@ def stats(**kwargs):
if kwargs.get('active') == 'true':
query = query.join(ELB.listeners)
query = query.filter(Listener.certificate_id != None)
query = query.filter(Listener.certificate_id != None) # noqa
items = query.group_by(attr).all()
@ -121,5 +122,3 @@ def stats(**kwargs):
if key:
results.append({"key": key, "y": count})
return results

View File

@ -1,72 +0,0 @@
"""
.. module: lemur.elbs.sync
:platform: Unix
:synopsis: This module attempts to sync with AWS and ensure that all elbs
currently available in AWS are available in Lemur as well
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import current_app
#from lemur.accounts import service as account_service
from lemur.elbs import service as elb_service
#from lemur.common.services.aws.elb import get_all_elbs, get_all_regions
def create_new(known, aws, account):
new = 0
for elb in aws:
for n in known:
if elb.name == n.name:
break
else:
new += 1
current_app.logger.debug("Creating {0}".format(elb.name))
try:
elb_service.create(account, elb)
except AttributeError as e:
current_app.logger.exception(e)
return new
def remove_missing(known, aws):
deleted = 0
for ke in known:
for elb in aws:
if elb.name == ke.name:
break
else:
deleted += 1
current_app.logger.debug("Deleting {0}".format(ke.name))
elb_service.delete(ke.id)
return deleted
def sync_all_elbs():
for account in account_service.get_all():
regions = get_all_regions()
for region in regions:
current_app.logger.info("Importing ELBs from '{0}/{1}/{2}'... ".format(account.account_number, account.label, region))
try:
aws_elbs = get_all_elbs(account.account_number, region)
except Exception as e:
current_app.logger.error("Failed to get ELBS from '{0}/{1}/{2}' reason: {3}".format(
account.label, account.account_number, region, e.message)
)
continue
known_elbs = elb_service.get_by_region_and_account(region, account.id)
new_elbs = create_new(known_elbs, aws_elbs, account)
current_app.logger.info(
"Created {0} new ELBs in '{1}/{2}/{3}'...".format(
new_elbs, account.account_number, account.label, region))
deleted_elbs = remove_missing(known_elbs, aws_elbs)
current_app.logger.info(
"Deleted {0} missing ELBs from '{1}/{2}/{3}'...".format(
deleted_elbs, account.account_number, account.label, region))

View File

@ -3,7 +3,6 @@
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
"""
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy()
@ -15,4 +14,3 @@ bcrypt = Bcrypt()
from flask.ext.principal import Principal
principal = Principal()

View File

@ -19,7 +19,6 @@ from logging.handlers import RotatingFileHandler
from flask import Flask
from lemur.common.health import mod as health
from lemur.exceptions import NoEncryptionKeyFound
from lemur.extensions import db, migrate, principal
@ -161,7 +160,6 @@ def install_plugins(app):
try:
plugin = ep.load()
except Exception:
import sys
import traceback
app.logger.error("Failed to load plugin %r:\n%s\n" % (ep.name, traceback.format_exc()))
else:

View File

@ -40,4 +40,3 @@ class Listener(db.Model):
blob = self.as_dict()
del blob['date_created']
return blob

View File

@ -60,7 +60,7 @@ def create(elb_id, instance_protocol, instance_port, load_balancer_port, load_ba
cert = verify_attachment(certificate_id, account_number)
listener_tuple = (load_balancer_port, instance_port, load_balancer_protocol, cert.get_art(account_number),)
create_new_listeners(account_number, elb.region, elb.name, [listener_tuple])
# create_new_listeners(account_number, elb.region, elb.name, [listener_tuple])
return {'message': 'Listener has been created'}
@ -98,7 +98,7 @@ def update(listener_id, **kwargs):
database.update(listener)
listener_tuple = (listener.load_balancer_port, listener.instance_port, listener.load_balancer_protocol, arn,)
update_listeners(account_number, elb.region, elb.name, [listener_tuple], ports)
# update_listeners(account_number, elb.region, elb.name, [listener_tuple], ports)
return {'message': 'Listener has been updated'}
@ -106,7 +106,7 @@ def update(listener_id, **kwargs):
def delete(listener_id):
# first try to delete the listener in aws
listener = get(listener_id)
delete_listeners(listener.elb.account.account_number, listener.elb.region, listener.elb.name, [listener.load_balancer_port])
# delete_listeners(listener.elb.account.account_number, listener.elb.region, listener.elb.name, [listener.load_balancer_port])
# cleanup operation in lemur
database.delete(listener)
@ -149,7 +149,7 @@ def stats(**kwargs):
query = query.filter(ELB.account_id == kwargs.get('account_id'))
if kwargs.get('active') == 'true':
query = query.filter(Listener.certificate_id != None)
query = query.filter(Listener.certificate_id != None) # noqa
items = query.group_by(attr).all()
results = []
@ -157,6 +157,3 @@ def stats(**kwargs):
if key:
results.append({"key": key, "y": count})
return results

View File

@ -20,19 +20,18 @@ from lemur.plugins.base import plugins
from lemur.certificates.verify import verify_string
from lemur.certificates import sync
from lemur.elbs.sync import sync_all_elbs
from lemur import create_app
# Needed to be imported so that SQLAlchemy create_all can find our models
from lemur.users.models import User
from lemur.roles.models import Role
from lemur.authorities.models import Authority
from lemur.certificates.models import Certificate
from lemur.destinations.models import Destination
from lemur.domains.models import Domain
from lemur.elbs.models import ELB
from lemur.listeners.models import Listener
from lemur.users.models import User # noqa
from lemur.roles.models import Role # noqa
from lemur.authorities.models import Authority # noqa
from lemur.certificates.models import Certificate # noqa
from lemur.destinations.models import Destination # noqa
from lemur.domains.models import Domain # noqa
from lemur.elbs.models import ELB # noqa
from lemur.listeners.models import Listener # noqa
manager = Manager(create_app)
manager.add_option('-c', '--config', dest='config')
@ -55,48 +54,42 @@ ADMINS = frozenset([''])
THREADS_PER_PAGE = 8
#############
## General ##
#############
# General
# These will need to be set to `True` if you are developing locally
CORS = False
debug = False
# this is the secret key used by flask session management
SECRET_KEY = '{flask_secret_key}'
# You should consider storing these separately from your config
LEMUR_SECRET_TOKEN = '{secret_token}'
LEMUR_TOKEN_SECRET = '{secret_token}'
LEMUR_ENCRYPTION_KEY = '{encryption_key}'
# this is a list of domains as regexes that only admins can issue
LEMUR_RESTRICTED_DOMAINS = []
#################
## Mail Server ##
#################
# Mail Server
# Lemur currently only supports SES for sending email, this address
# needs to be verified
LEMUR_EMAIL = ''
LEMUR_SECURITY_TEAM_EMAIL = []
#############
## Logging ##
#############
# Logging
LOG_LEVEL = "DEBUG"
LOG_FILE = "lemur.log"
##############
## Database ##
##############
# Database
SQLALCHEMY_DATABASE_URI = ''
# modify this if you are not using a local database
SQLALCHEMY_DATABASE_URI = 'postgresql://lemur:lemur@localhost:5432/lemur'
#########
## AWS ##
#########
# AWS
# Lemur will need STS assume role access to every destination you want to monitor
#AWS_ACCOUNT_MAPPINGS = {{
@ -129,6 +122,7 @@ SQLALCHEMY_DATABASE_URI = ''
#VERSIGN_EMAIL = ''
"""
@MigrateCommand.command
def create():
database.db.create_all()
@ -178,7 +172,8 @@ def generate_settings():
"""
output = CONFIG_TEMPLATE.format(
encryption_key=base64.b64encode(os.urandom(KEY_LENGTH)),
secret_token=base64.b64encode(os.urandom(KEY_LENGTH))
secret_token=base64.b64encode(os.urandom(KEY_LENGTH)),
flask_secret_key=base64.b64encode(os.urandom(KEY_LENGTH)),
)
return output
@ -480,8 +475,8 @@ def main():
manager.add_command("show_urls", ShowUrls())
manager.add_command("db", MigrateCommand)
manager.add_command("init", InitializeApp())
manager.add_command('create_user', CreateUser())
manager.add_command('create_role', CreateRole())
manager.add_command("create_user", CreateUser())
manager.add_command("create_role", CreateRole())
manager.add_command("sync", Sync())
manager.run()

View File

@ -0,0 +1,49 @@
"""Refactors Accounts to Destinations
Revision ID: 3b718f59b8ce
Revises: None
Create Date: 2015-07-09 17:44:55.626221
"""
# revision identifiers, used by Alembic.
revision = '3b718f59b8ce'
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_table('certificate_account_associations')
op.drop_table('accounts')
op.add_column('destinations', sa.Column('plugin_name', sa.String(length=32), nullable=True))
op.drop_index('ix_elbs_account_id', table_name='elbs')
op.drop_constraint(u'elbs_account_id_fkey', 'elbs', type_='foreignkey')
op.drop_column('elbs', 'account_id')
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column('elbs', sa.Column('account_id', sa.BIGINT(), autoincrement=False, nullable=True))
op.create_foreign_key(u'elbs_account_id_fkey', 'elbs', 'accounts', ['account_id'], ['id'])
op.create_index('ix_elbs_account_id', 'elbs', ['account_id'], unique=False)
op.drop_column('destinations', 'plugin_name')
op.create_table('accounts',
sa.Column('id', sa.INTEGER(), server_default=sa.text(u"nextval('accounts_id_seq'::regclass)"), nullable=False),
sa.Column('account_number', sa.VARCHAR(length=32), autoincrement=False, nullable=True),
sa.Column('label', sa.VARCHAR(length=32), autoincrement=False, nullable=True),
sa.Column('notes', sa.TEXT(), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name=u'accounts_pkey'),
sa.UniqueConstraint('account_number', name=u'accounts_account_number_key'),
postgresql_ignore_search_path=False
)
op.create_table('certificate_account_associations',
sa.Column('account_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('certificate_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['account_id'], [u'accounts.id'], name=u'certificate_account_associations_account_id_fkey', ondelete=u'CASCADE'),
sa.ForeignKeyConstraint(['certificate_id'], [u'certificates.id'], name=u'certificate_account_associations_certificate_id_fkey', ondelete=u'CASCADE')
)
### end Alembic commands ###

View File

@ -19,12 +19,13 @@ certificate_associations = db.Table('certificate_associations',
)
certificate_destination_associations = db.Table('certificate_destination_associations',
Column('destination_id', Integer, ForeignKey('destinations.id', ondelete='cascade')),
Column('certificate_id', Integer, ForeignKey('certificates.id', ondelete='cascade'))
Column('destination_id', Integer,
ForeignKey('destinations.id', ondelete='cascade')),
Column('certificate_id', Integer,
ForeignKey('certificates.id', ondelete='cascade'))
)
roles_users = db.Table('roles_users',
Column('user_id', Integer, ForeignKey('users.id')),
Column('role_id', Integer, ForeignKey('roles.id'))
)

View File

@ -58,7 +58,7 @@ def _find_superseded(domains):
current_app.logger.info("Trying to resolve {0}".format(domain.name))
query = query.filter(Certificate.domains.any(Domain.name.in_([x.name for x in domains])))
query = query.filter(Certificate.active == True)
query = query.filter(Certificate.active == True) # noqa
query = query.filter(Certificate.not_after >= arrow.utcnow().format('YYYY-MM-DD'))
ss_list.extend(query.all())

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from lemur.plugins.base import * # NOQA
from lemur.plugins.bases import * # NOQA
from lemur.plugins.base import * # noqa
from lemur.plugins.bases import * # noqa

View File

@ -9,7 +9,7 @@
from __future__ import absolute_import, print_function
from lemur.plugins.base.manager import PluginManager
from lemur.plugins.base.v1 import * # NOQA
from lemur.plugins.base.v1 import * # noqa
plugins = PluginManager()
register = plugins.register

View File

@ -8,6 +8,7 @@
from flask import current_app
from lemur.common.managers import InstanceManager
# inspired by https://github.com/getsentry/sentry
class PluginManager(InstanceManager):
def __iter__(self):
@ -57,4 +58,3 @@ class PluginManager(InstanceManager):
def unregister(self, cls):
self.remove('%s.%s' % (cls.__module__, cls.__name__))
return cls

View File

@ -8,6 +8,7 @@
"""
from threading import local
# stolen from https://github.com/getsentry/sentry/
class PluginMount(type):
def __new__(cls, name, bases, attrs):

View File

@ -1,3 +1,3 @@
from .destination import DestinationPlugin # NOQA
from .issuer import IssuerPlugin # NOQA
from .source import SourcePlugin
from .destination import DestinationPlugin # noqa
from .issuer import IssuerPlugin # noqa
from .source import SourcePlugin # noqa

View File

@ -8,9 +8,9 @@
"""
from lemur.plugins.base import Plugin
class DestinationPlugin(Plugin):
type = 'destination'
def upload(self):
raise NotImplemented

View File

@ -8,6 +8,7 @@
"""
from lemur.plugins.base import Plugin
class IssuerPlugin(Plugin):
"""
This is the base class from which all of the supported
@ -20,4 +21,3 @@ class IssuerPlugin(Plugin):
def create_authority(self):
raise NotImplemented

View File

@ -8,6 +8,7 @@
"""
from lemur.plugins.base import Plugin
class SourcePlugin(Plugin):
type = 'source'
@ -16,4 +17,3 @@ class SourcePlugin(Plugin):
def get_options(self):
return {}

View File

@ -38,6 +38,7 @@ def is_valid(listener_tuple):
return listener_tuple
def get_all_regions():
"""
Retrieves all current EC2 regions.
@ -49,6 +50,7 @@ def get_all_regions():
regions.append(r.name)
return regions
def get_all_elbs(account_number, region):
"""
Fetches all elb objects for a given account and region.
@ -74,7 +76,6 @@ def get_all_elbs(account_number, region):
# return elbs
def attach_certificate(account_number, region, name, port, certificate_id):
"""
Attaches a certificate to a listener, throws exception
@ -137,4 +138,3 @@ def delete_listeners(account_number, region, name, ports):
:return:
"""
return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)

View File

@ -1,5 +1,5 @@
"""
.. module: lemur.common.services.aws.iam
.. module: lemur.plugins.lemur_aws.iam
:platform: Unix
:synopsis: Contains helper functions for interactive with AWS IAM Apis.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
@ -19,21 +19,6 @@ def get_name_from_arn(arn):
return arn.split("/", 1)[1]
def ssl_split(param_string):
"""
:param param_string:
:return:
"""
output = {}
parts = str(param_string).split("/")
for part in parts:
if "=" in part:
key, value = part.split("=", 1)
output[key] = value
return output
def upload_cert(account_number, cert, private_key, cert_chain=None):
"""
Upload a certificate to AWS
@ -44,7 +29,8 @@ def upload_cert(account_number, cert, private_key, cert_chain=None):
:param cert_chain:
:return:
"""
return assume_service(account_number, 'iam').upload_server_cert(cert.name, str(cert.body), str(private_key), cert_chain=str(cert_chain))
return assume_service(account_number, 'iam').upload_server_cert(cert.name, str(cert.body), str(private_key),
cert_chain=str(cert_chain))
def delete_cert(account_number, cert):
@ -109,5 +95,3 @@ def digest_aws_cert_response(response):
chain = cert['certificate_chain']
return str(body), str(chain),

View File

@ -58,10 +58,22 @@ class AWSSourcePlugin(SourcePlugin):
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
options = {
'accountNumber': {'type': 'int'},
'pollRate': {'type': 'int', 'default': '60'}
options = [
{
'name': 'accountNumber',
'type': 'int',
'required': True,
'validation': '/^[0-9]{12,12}$/',
'helpMessage': 'Must be a valid AWS account number!',
},
{
'name': 'pollRate',
'type': 'int',
'required': False,
'helpMessage': 'Rate in seconds to poll source for new information.',
'default': '60',
}
]
def get_certificates(self, **kwargs):
certs = []

View File

@ -35,7 +35,3 @@ def assume_service(account_number, service, region=None):
aws_access_key_id=role.credentials.access_key,
aws_secret_access_key=role.credentials.secret_key,
security_token=role.credentials.session_token)

View File

@ -0,0 +1,31 @@
from moto import mock_iam, mock_sts
from lemur.certificates.models import Certificate
from lemur.tests.certs import EXTERNAL_VALID_STR, PRIVATE_KEY_STR
def test_get_name_from_arn():
from lemur.plugins.lemur_aws.iam import get_name_from_arn
arn = 'arn:aws:iam::123456789012:server-certificate/tttt2.netflixtest.net-NetflixInc-20150624-20150625'
assert get_name_from_arn(arn) == 'tttt2.netflixtest.net-NetflixInc-20150624-20150625'
@mock_sts()
@mock_iam()
def test_get_all_server_certs(app):
from lemur.plugins.lemur_aws.iam import upload_cert, get_all_server_certs
cert = Certificate(EXTERNAL_VALID_STR)
upload_cert('123456789012', cert, PRIVATE_KEY_STR)
certs = get_all_server_certs('123456789012')
assert len(certs) == 1
@mock_sts()
@mock_iam()
def test_get_cert_from_arn(app):
from lemur.plugins.lemur_aws.iam import upload_cert, get_cert_from_arn
cert = Certificate(EXTERNAL_VALID_STR)
upload_cert('123456789012', cert, PRIVATE_KEY_STR)
body, chain = get_cert_from_arn('arn:aws:iam::123456789012:server-certificate/tttt2.netflixtest.net-NetflixInc-20150624-20150625')
assert body.replace('\n', '') == EXTERNAL_VALID_STR.replace('\n', '')

View File

@ -72,7 +72,8 @@ def get_default_issuance(options):
if not options.get('validityStart') and not options.get('validityEnd'):
start = arrow.utcnow()
options['validityStart'] = start.floor('second').isoformat()
options['validityEnd'] = start.replace(years=current_app.config.get('CLOUDCA_DEFAULT_VALIDITY')).ceil('second').isoformat()
options['validityEnd'] = start.replace(years=current_app.config.get('CLOUDCA_DEFAULT_VALIDITY'))\
.ceil('second').isoformat()
return options
@ -95,7 +96,8 @@ def convert_date_to_utc_time(date):
:return:
"""
d = arrow.get(date)
return arrow.utcnow().replace(day=d.naive.day).replace(month=d.naive.month).replace(year=d.naive.year).replace(microsecond=0)
return arrow.utcnow().replace(day=d.naive.day).replace(month=d.naive.month).replace(year=d.naive.year)\
.replace(microsecond=0)
def process_response(response):
@ -152,7 +154,9 @@ class CloudCA(object):
self.session.cert = current_app.config.get('CLOUDCA_PEM_PATH')
self.ca_bundle = current_app.config.get('CLOUDCA_BUNDLE')
else:
current_app.logger.warning("No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA")
current_app.logger.warning(
"No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA"
)
super(CloudCA, self).__init__(*args, **kwargs)
@ -203,7 +207,7 @@ class CloudCA(object):
for ca in self.get(endpoint)['data']['caList']:
try:
authorities.append(ca['caName'])
except AttributeError as e:
except AttributeError:
current_app.logger.error("No authority has been defined for {}".format(ca['caName']))
return authorities
@ -235,7 +239,8 @@ class CloudCAIssuerPlugin(IssuerPlugin, CloudCA):
options['validityStart'] = convert_date_to_utc_time(options['validityStart']).isoformat()
options['validityEnd'] = convert_date_to_utc_time(options['validityEnd']).isoformat()
response = self.session.post(self.url + endpoint, data=dumps(remove_none(options)), timeout=10, verify=self.ca_bundle)
response = self.session.post(self.url + endpoint, data=dumps(remove_none(options)), timeout=10,
verify=self.ca_bundle)
json = process_response(response)
roles = []
@ -326,7 +331,8 @@ class CloudCASourcePlugin(SourcePlugin, CloudCA):
:return:
"""
endpoint = '{0}/getCert'.format(API_ENDPOINT)
response = self.session.post(self.url + endpoint, data=dumps({'caName': ca_name}), timeout=10, verify=self.ca_bundle)
response = self.session.post(self.url + endpoint, data=dumps({'caName': ca_name}), timeout=10,
verify=self.ca_bundle)
raw = process_response(response)
certs = []

View File

@ -55,4 +55,3 @@ F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
"""

View File

@ -16,6 +16,7 @@ from flask import current_app
from lemur.plugins.bases import IssuerPlugin
from lemur.plugins import lemur_verisign as verisign
from lemur.plugins.lemur_verisign import constants
from lemur.common.utils import get_psuedo_random_string
# https://support.venafi.com/entries/66445046-Info-VeriSign-Error-Codes
@ -58,9 +59,57 @@ VERISIGN_ERRORS = {
}
def process_options(options):
"""
Processes and maps the incoming issuer options to fields/options that
verisign understands
:param options:
:return: dict or valid verisign options
"""
data = {
'challenge': get_psuedo_random_string(),
'serverType': 'Apache',
'certProductType': 'Server',
'firstName': current_app.config.get("VERISIGN_FIRST_NAME"),
'lastName': current_app.config.get("VERISIGN_LAST_NAME"),
'signatureAlgorithm': 'sha256WithRSAEncryption',
'email': current_app.config.get("VERISIGN_EMAIL")
}
if options.get('validityEnd'):
end_date, period = get_default_issuance(options)
data['specificEndDate'] = end_date
data['validityPeriod'] = period
return data
def get_default_issuance(options):
"""
Gets the default time range for certificates
:param options:
:return:
"""
specific_end_date = arrow.get(options['validityEnd']).replace(days=-1).format("MM/DD/YYYY")
now = arrow.utcnow()
then = arrow.get(options['validityEnd'])
if then < now.replace(years=+1):
validity_period = '1Y'
elif then < now.replace(years=+2):
validity_period = '2Y'
else:
raise Exception("Verisign issued certificates cannot exceed two years in validity")
return specific_end_date, validity_period
def handle_response(content):
"""
Helper function that helps with parsing responses from the Verisign API.
Helper function for parsing responses from the Verisign API.
:param content:
:return: :raise Exception:
"""
@ -99,29 +148,8 @@ class VerisignIssuerPlugin(IssuerPlugin):
"""
url = current_app.config.get("VERISIGN_URL") + '/enroll'
data = {
'csr': csr,
'challenge': issuer_options['challenge'],
'serverType': 'Apache',
'certProductType': 'Server',
'firstName': current_app.config.get("VERISIGN_FIRST_NAME"),
'lastName': current_app.config.get("VERISIGN_LAST_NAME"),
'signatureAlgorithm': 'sha256WithRSAEncryption',
'email': current_app.config.get("VERISIGN_EMAIL")
}
if issuer_options.get('validityEnd'):
data['specificEndDate'] = arrow.get(issuer_options['validityEnd']).replace(days=-1).format("MM/DD/YYYY")
now = arrow.utcnow()
then = arrow.get(issuer_options['validityEnd'])
if then < now.replace(years=+1):
data['validityPeriod'] = '1Y'
elif then < now.replace(years=+2):
data['validityPeriod'] = '2Y'
else:
raise Exception("Verisign issued certificates cannot exceed two years in validity")
data = process_options(issuer_options)
data['csr'] = csr
current_app.logger.info("Requesting a new verisign certificate: {0}".format(data))
@ -151,4 +179,3 @@ class VerisignIssuerPlugin(IssuerPlugin):
url = current_app.config.get("VERISIGN_URL") + '/getTokens'
response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'})
return handle_response(response.content)['Response']['Order']

View File

@ -137,4 +137,3 @@ class PluginsTypeList(AuthenticatedResource):
api.add_resource(PluginsList, '/plugins', endpoint='plugins')
api.add_resource(PluginsTypeList, '/plugins/<plugin_type>', endpoint='pluginType')

View File

@ -36,4 +36,3 @@ class Role(db.Model):
def serialize(self):
blob = self.as_dict()
return blob

View File

@ -15,6 +15,7 @@ from lemur import database
from lemur.roles.models import Role
from lemur.users.models import User
def update(role_id, name, description, users):
"""
Update a role
@ -122,4 +123,3 @@ def render(args):
query = database.sort(query, Role, sort_by, sort_dir)
return database.paginate(query, page, count)

View File

@ -65,11 +65,11 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
RestangularConfigurer.setBaseUrl('http://localhost:5000/api/1');
RestangularConfigurer.setDefaultHttpFields({withCredentials: true});
RestangularConfigurer.addResponseInterceptor(function (data, operation, what, url, response, deferred) {
RestangularConfigurer.addResponseInterceptor(function (data, operation) {
var extractedData;
// .. to look for getList operations
if (operation === "getList") {
if (operation === 'getList') {
// .. and handle the data and meta data
extractedData = data.items;
extractedData.total = data.total;
@ -79,7 +79,7 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
return extractedData;
});
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) {
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');
@ -97,7 +97,7 @@ lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
newParams.sortDir = params[item];
} else if (item.indexOf(f) > -1) {
var key = regExp.exec(item)[1];
newParams['filter'] = key + ";" + params[item];
newParams.filter = key + ';' + params[item];
} else {
newParams[item] = params[item];
}

View File

@ -28,7 +28,7 @@ angular.module('lemur')
AuthenticationService.authenticate = function (provider) {
$auth.authenticate(provider)
.then(
function (user) {
function () {
UserService.getCurrentUser();
$rootScope.$emit('user:login');
$location.url('/certificates');
@ -41,7 +41,7 @@ angular.module('lemur')
});
}
);
}
};
AuthenticationService.logout = function () {
if (!$auth.isAuthenticated()) {
@ -56,7 +56,7 @@ angular.module('lemur')
body: 'You have been successfully logged out.'
});
$location.path('/');
})
});
};
});

View File

@ -19,10 +19,10 @@ angular.module('lemur')
$scope.loading = false;
$scope.create = function (authority) {
WizardHandler.wizard().context.loading = true;
AuthorityService.create(authority).then(function (resposne) {
AuthorityService.create(authority).then(function () {
WizardHandler.wizard().context.loading = false;
$modalInstance.close();
})
});
};
PluginService.get('issuer').then(function (plugins) {

View File

@ -35,7 +35,7 @@ angular.module('lemur')
$scope.getAuthorityStatus = function () {
var def = $q.defer();
def.resolve([{'title': 'Active', 'id': true}, {'title': 'Inactive', 'id': false}])
def.resolve([{'title': 'Active', 'id': true}, {'title': 'Inactive', 'id': false}]);
return def;
};

View File

@ -16,7 +16,7 @@ angular.module('lemur')
$scope.create = function (certificate) {
WizardHandler.wizard().context.loading = true;
CertificateService.create(certificate).then(function (response) {
CertificateService.create(certificate).then(function () {
WizardHandler.wizard().context.loading = false;
$modalInstance.close();
});

View File

@ -1,6 +1,5 @@
/**
* Created by kglisson on 1/19/15.
*/
'use strict';
angular.module('lemur')
.service('CertificateApi', function (LemurRestangular, DomainService) {
LemurRestangular.extendModel('certificates', function (obj) {
@ -102,7 +101,7 @@ angular.module('lemur')
CertificateService.create = function (certificate) {
certificate.attachSubAltName();
return CertificateApi.post(certificate).then(
function (response) {
function () {
toaster.pop({
type: 'success',
title: certificate.name,
@ -132,8 +131,8 @@ angular.module('lemur')
};
CertificateService.upload = function (certificate) {
CertificateApi.customPOST(certificate, "upload").then(
function (response) {
CertificateApi.customPOST(certificate, 'upload').then(
function () {
toaster.pop({
type: 'success',
title: certificate.name,
@ -163,7 +162,7 @@ angular.module('lemur')
certificate.privateKey = response.key;
}
},
function (response) {
function () {
toaster.pop({
type: 'error',
title: certificate.name,

View File

@ -48,7 +48,7 @@ angular.module('lemur')
$scope.getCertificateStatus = function () {
var def = $q.defer();
def.resolve([{'title': 'Active', 'id': true}, {'title': 'Inactive', 'id': false}])
def.resolve([{'title': 'Active', 'id': true}, {'title': 'Inactive', 'id': false}]);
return def;
};

View File

@ -1,3 +1,5 @@
'use strict';
angular.module('lemur').
filter('titleCase', function () {
return function (str) {

View File

@ -7,9 +7,8 @@ angular.module('lemur')
controller: 'DashboardController'
});
})
.controller('DashboardController', function ($scope, $rootScope, $filter, $location, LemurRestangular, ngTableParams) {
.controller('DashboardController', function ($scope, $rootScope, $filter, $location, LemurRestangular) {
var baseStats = LemurRestangular.all('stats');
var baseAccounts = LemurRestangular.all('accounts');
baseAccounts.getList()
@ -78,16 +77,16 @@ angular.module('lemur')
LemurRestangular.all('certificates').customGET('stats', {metric: 'issuer'})
.then(function (data) {
$scope.issuers = data['items'];
$scope.issuers = data.items;
});
LemurRestangular.all('certificates').customGET('stats', {metric: 'bits'})
.then(function (data) {
$scope.bits = data['items'];
$scope.bits = data.items;
});
LemurRestangular.all('certificates').customGET('stats', {metric: 'not_after'})
.then(function (data) {
$scope.expiring = {labels: data['items']['labels'], values: [data['items']['values']]};
$scope.expiring = {labels: data.items.labels, values: [data.items.values]};
});
});

View File

@ -32,7 +32,7 @@ angular.module('lemur')
DestinationService.update(destination).then(function () {
$modalInstance.close();
});
}
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');

View File

@ -1,3 +1,5 @@
'use strict';
angular.module('lemur')
.service('DomainApi', function (LemurRestangular) {
return LemurRestangular.all('domains');

View File

@ -1,5 +1,7 @@
'use strict';
angular.module('lemur')
.service('ELBApi', function (LemurRestangular, ListenerService) {
.service('ELBApi', function (LemurRestangular) {
LemurRestangular.extendModel('elbs', function (obj) {
return angular.extend(obj, {
attachListener: function (listener) {

View File

@ -1,3 +1,5 @@
'use strict';
angular.module('lemur')
.service('ListenerApi', function (LemurRestangular) {
return LemurRestangular.all('listeners');

View File

@ -0,0 +1,14 @@
'use strict';
angular.module('lemur')
.service('PluginApi', function (LemurRestangular) {
return LemurRestangular.all('plugins');
})
.service('PluginService', function (PluginApi) {
var PluginService = this;
PluginService.get = function (type) {
return PluginApi.customGETLIST(type).then(function (plugins) {
return plugins;
});
};
});

View File

@ -1,3 +1,5 @@
'use strict';
angular.module('lemur')
.service('RoleApi', function (LemurRestangular) {
LemurRestangular.extendModel('roles', function (obj) {
@ -108,7 +110,7 @@ angular.module('lemur')
role.username = response.username;
}
},
function (response) {
function () {
toaster.pop({
type: 'error',
title: role.name,

View File

@ -18,7 +18,7 @@ angular.module('lemur')
UserService.update(user).then(function () {
$modalInstance.close();
});
}
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');

View File

@ -1,4 +1,9 @@
import unittest
class LemurTestCase(unittest.TestCase):
pass
class LemurPluginTestCase(LemurTestCase):
pass

68
lemur/tests/conf.py Normal file
View File

@ -0,0 +1,68 @@
# This is just Python which means you can inherit and tweak settings
import os
_basedir = os.path.abspath(os.path.dirname(__file__))
ADMINS = frozenset([''])
THREADS_PER_PAGE = 8
# General
# These will need to be set to `True` if you are developing locally
CORS = False
debug = False
TESTING = True
# this is the secret key used by flask session management
SECRET_KEY = 'I/dVhOZNSMZMqrFJa5tWli6VQccOGudKerq3eWPMSzQNmHHVhMAQfQ=='
# You should consider storing these separately from your config
LEMUR_TOKEN_SECRET = 'test'
LEMUR_ENCRYPTION_KEY = 'jPd2xwxgVGXONqghHNq7/S761sffYSrT3UAgKwgtMxbqa0gmKYCfag=='
# this is a list of domains as regexes that only admins can issue
LEMUR_RESTRICTED_DOMAINS = []
# Mail Server
# Lemur currently only supports SES for sending email, this address
# needs to be verified
LEMUR_EMAIL = ''
LEMUR_SECURITY_TEAM_EMAIL = []
# Logging
LOG_LEVEL = "DEBUG"
LOG_FILE = "lemur.log"
# Database
# modify this if you are not using a local database
SQLALCHEMY_DATABASE_URI = 'postgresql://lemur:lemur@localhost:5432/lemur'
# AWS
LEMUR_INSTANCE_PROFILE = 'Lemur'
# Issuers
# These will be dependent on which 3rd party that Lemur is
# configured to use.
# CLOUDCA_URL = ''
# CLOUDCA_PEM_PATH = ''
# CLOUDCA_BUNDLE = ''
# number of years to issue if not specified
# CLOUDCA_DEFAULT_VALIDITY = 2
VERISIGN_URL = 'http://example.com'
VERISIGN_PEM_PATH = '~/'
VERISIGN_FIRST_NAME = 'Jim'
VERISIGN_LAST_NAME = 'Bob'
VERSIGN_EMAIL = 'jim@example.com'

View File

@ -1,3 +1,4 @@
import os
import pytest
from lemur import create_app
@ -33,14 +34,11 @@ def app():
Creates a new Flask application for a test duration.
Uses application factory `create_app`.
"""
app = create_app()
app.config['TESTING'] = True
app.config['LEMUR_ENCRYPTION_KEY'] = 'test'
ctx = app.app_context()
_app = create_app(os.path.dirname(os.path.realpath(__file__)) + '/conf.py')
ctx = _app.app_context()
ctx.push()
yield app
yield _app
ctx.pop()
@ -73,4 +71,3 @@ def session(db, request):
@pytest.yield_fixture(scope="function")
def client(app, session, client):
yield client

View File

@ -1,5 +1,4 @@
import pytest
from lemur.authorities.views import *
from lemur.authorities.views import * # noqa
# def test_crud(session):
# role = create('role1')
@ -149,15 +148,3 @@ def test_admin_authorities_delete(client):
def test_admin_certificate_authorities_get(client):
assert client.get(api.url_for(CertificateAuthority, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 404
def test_admin_certificate_authorities_post(client):
assert client.post(api.url_for(CertificateAuthority, certficate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_certificate_authorities_put(client):
assert client.put(api.url_for(CertificateAuthority, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_certificate_authorities_delete(client):
assert client.delete(api.url_for(CertificateAuthority, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405

View File

@ -1,8 +1,5 @@
import pytest
from lemur.certificates.views import *
def test_valid_authority(session):
assert 1 == 2
from lemur.certificates.views import * # noqa
def test_pem_str():
@ -40,18 +37,6 @@ def test_create_basic_csr():
assert name.value in csr_config.values()
def test_import_certificate():
assert 1 == 2
def test_mint():
assert 1 == 2
def test_disassociate_aws_account():
assert 1 == 2
def test_cert_get_cn():
from lemur.tests.certs import INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_get_cn
@ -59,19 +44,19 @@ def test_cert_get_cn():
assert cert_get_cn(INTERNAL_VALID_LONG_CERT) == 'long.lived.com'
def test_cert_get_domains():
def test_cert_get_subAltDomains():
from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_get_domains
assert cert_get_domains(INTERNAL_VALID_LONG_CERT) == ['long.lived.com']
assert cert_get_domains(INTERNAL_VALID_SAN_CERT) == ['example2.long.com', 'example3.long.com', 'san.example.com']
assert cert_get_domains(INTERNAL_VALID_LONG_CERT) == []
assert cert_get_domains(INTERNAL_VALID_SAN_CERT) == ['example2.long.com', 'example3.long.com']
def test_cert_is_san():
from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_is_san
assert cert_is_san(INTERNAL_VALID_LONG_CERT) == False
assert cert_is_san(INTERNAL_VALID_LONG_CERT) == None
assert cert_is_san(INTERNAL_VALID_SAN_CERT) == True
@ -79,7 +64,7 @@ def test_cert_is_wildcard():
from lemur.tests.certs import INTERNAL_VALID_WILDCARD_CERT, INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_is_wildcard
assert cert_is_wildcard(INTERNAL_VALID_WILDCARD_CERT) == True
assert cert_is_wildcard(INTERNAL_VALID_LONG_CERT) == False
assert cert_is_wildcard(INTERNAL_VALID_LONG_CERT) == None
def test_cert_get_bitstrength():
@ -87,6 +72,7 @@ def test_cert_get_bitstrength():
from lemur.certificates.models import cert_get_bitstrength
assert cert_get_bitstrength(INTERNAL_VALID_LONG_CERT) == 2048
def test_cert_get_issuer():
from lemur.tests.certs import INTERNAL_VALID_LONG_CERT
from lemur.certificates.models import cert_get_issuer
@ -324,4 +310,3 @@ def test_admin_certificate_credentials_delete(client):
def test_admin_certificate_credentials_patch(client):
assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405

View File

@ -1,38 +0,0 @@
TEST_CSR = """
# Configuration for standard CSR generation for Netflix
# Used for procuring VeriSign certificates
# Author: jachan
# Contact: cloudsecurity@netflix.com
[ req ]
# Use a 2048 bit private key
default_bits = 2048
default_keyfile = key.pem
prompt = no
encrypt_key = no
# base request
distinguished_name = req_distinguished_name
# extensions
# Uncomment the following line if you are requesting a SAN cert
{is_san_comment}req_extensions = req_ext
# distinguished_name
[ req_distinguished_name ]
countryName = "US" # C=
stateOrProvinceName = "CALIFORNIA" # ST=
localityName = "Los Gatos" # L=
organizationName = "Netflix, Inc." # O=
organizationalUnitName = "Operations" # OU=
# This is the hostname/subject name on the certificate
commonName = "{DNS[0]}" # CN=
[ req_ext ]
# Uncomment the following line if you are requesting a SAN cert
{is_san_comment}subjectAltName = @alt_names
[alt_names]
# Put your SANs here
{DNS_LINES}
"""

View File

@ -1,15 +1,15 @@
from lemur.destinations.service import *
from lemur.destinations.views import *
from lemur.destinations.service import * # noqa
from lemur.destinations.views import * # noqa
from json import dumps
def test_crud(session):
destination = create('111111', 'destination1')
destination = create('testdest', 'aws-destination', {}, description='destination1')
assert destination.id > 0
destination = update(destination.id, 11111, 'destination2')
assert destination.label == 'destination2'
destination = update(destination.id, 'testdest2', {}, 'destination2')
assert destination.label == 'testdest2'
assert len(get_all()) == 1
@ -40,6 +40,7 @@ def test_destination_patch(client):
VALID_USER_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
def test_auth_destination_get(client):
assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
@ -63,6 +64,7 @@ def test_auth_destination_patch(client):
VALID_ADMIN_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
def test_admin_destination_get(client):
assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
@ -76,7 +78,7 @@ def test_admin_destination_put(client):
def test_admin_destination_delete(client):
assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500
assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
def test_admin_destination_patch(client):
@ -119,13 +121,13 @@ def test_admin_destinations_get(client):
def test_admin_destinations_crud(client):
assert client.post(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
data = {'destinationNumber': 111, 'label': 'test', 'comments': 'test'}
data = {'plugin': {'slug': 'aws-destination', 'pluginOptions': {}}, 'label': 'test', 'description': 'test'}
resp = client.post(api.url_for(DestinationsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN)
assert resp.status_code == 200
assert client.get(api.url_for(Destinations, destination_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN)
assert resp.status_code == 200
assert resp.json == {'items': [{'destinationNumber': 111, 'label': 'test', 'comments': 'test', 'id': 2}], 'total': 1}
assert resp.json['items'][0]['description'] == 'test'
assert client.delete(api.url_for(Destinations, destination_id=2), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN)
assert resp.status_code == 200

View File

@ -1,4 +1,5 @@
from lemur.domains.views import *
from lemur.domains.views import * # noqa
def test_domain_get(client):
assert client.get(api.url_for(Domains, domain_id=1)).status_code == 401
@ -23,6 +24,7 @@ def test_domain_patch(client):
VALID_USER_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
def test_auth_domain_get(client):
assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
@ -46,6 +48,7 @@ def test_auth_domain_patch(client):
VALID_ADMIN_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
def test_admin_domain_get(client):
assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
@ -119,5 +122,6 @@ def test_certificate_domains_patch(client):
def test_auth_certificate_domains_get(client):
assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
def test_admin_certificate_domains_get(client):
assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200

View File

@ -1,51 +0,0 @@
import boto
from lemur.tests import LemurTestCase
from moto import mock_elb, mock_sts
#class ELBTestCase(LemurTestCase):
# @mock_sts
# @mock_elb
# def test_add_listener(self):
# from lemur.common.services.aws.elb import create_new_listeners
# conn = boto.connect_elb()
# zones = ['us-east-1a', 'us-east-1b']
# ports = [(80, 8080, 'http')]
# conn.create_load_balancer('my-lb', zones, ports)
# create_new_listeners('111', 'us-east-1', 'my-lb', listeners=[('443', '80', 'HTTP')])
# balancer = conn.get_all_load_balancers()[0]
# self.assertEqual(balancer.name, "my-lb")
# self.assertEqual(len(balancer.listeners), 2)
#
# @mock_sts
# @mock_elb
# def test_update_listener(self):
# from lemur.common.services.aws.elb import update_listeners
# conn = boto.connect_elb()
# zones = ['us-east-1a', 'us-east-1b']
# ports = [(80, 8080, 'http')]
# conn.create_load_balancer('my-lb', zones, ports)
# update_listeners('111', 'us-east-1', 'my-lb', listeners=[('80', '7001', 'http')])
# balancer = conn.get_all_load_balancers()[0]
# listener = balancer.listeners[0]
# self.assertEqual(listener.load_balancer_port, 80)
# self.assertEqual(listener.instance_port, 7001)
# self.assertEqual(listener.protocol, "HTTP")
#
# @mock_sts
# @mock_elb
# def test_set_certificate(self):
# from lemur.common.services.aws.elb import attach_certificate
# conn = boto.connect_elb()
# zones = ['us-east-1a', 'us-east-1b']
# ports = [(443, 7001, 'https', 'sslcert')]
# conn.create_load_balancer('my-lb', zones, ports)
# attach_certificate('1111', 'us-east-1', 'my-lb', 443, 'somecert')
# balancer = conn.get_all_load_balancers()[0]
# listener = balancer.listeners[0]
# self.assertEqual(listener.load_balancer_port, 443)
# self.assertEqual(listener.instance_port, 7001)
# self.assertEqual(listener.protocol, "HTTPS")
# self.assertEqual(listener.ssl_certificate_id, 'somecert')
#

View File

@ -1,35 +0,0 @@
from lemur.tests import LemurTestCase
from lemur.certificates.models import Certificate
from moto import mock_iam, mock_sts
#class IAMTestCase(LemurTestCase):
# @mock_sts
# @mock_iam
# def test_get_all_server_certs(self):
# from lemur.common.services.aws.iam import upload_cert, get_all_server_certs
# cert = Certificate(TEST_CERT)
# upload_cert('1111', cert, TEST_KEY)
# certs = get_all_server_certs('1111')
# self.assertEquals(len(certs), 1)
#
# @mock_sts
# @mock_iam
# def test_get_server_cert(self):
# from lemur.common.services.aws.iam import upload_cert, get_cert_from_arn
# cert = Certificate(TEST_CERT)
# upload_cert('1111', cert, TEST_KEY)
# body, chain = get_cert_from_arn('arn:aws:iam::123456789012:server-certificate/AHB-dfdsflkj.net-NetflixInc-20140525-20150525')
# self.assertTrue(body)
#
# @mock_sts
# @mock_iam
# def test_upload_server_cert(self):
# from lemur.common.services.aws.iam import upload_cert
# cert = Certificate(TEST_CERT)
# response = upload_cert('1111', cert, TEST_KEY)
# self.assertEquals(response['upload_server_certificate_response']['upload_server_certificate_result']['server_certificate_metadata']['server_certificate_name'], 'AHB-dfdsflkj.net-NetflixInc-20140525-20150525')
#
#

View File

@ -1,4 +1,4 @@
from lemur.tests import LemurTestCase
# from lemur.tests import LemurTestCase
# class ManagerTestCase(LemurTestCase):
# def test_validate_authority(self):

View File

@ -1,6 +1,6 @@
from json import dumps
from lemur.roles.service import *
from lemur.roles.views import *
from lemur.roles.service import * # noqa
from lemur.roles.views import * # noqa
def test_crud(session):

View File

@ -84,5 +84,3 @@ class User(db.Model):
listen(User, 'before_insert', hash_password)

View File

@ -145,5 +145,3 @@ def render(args):
query = database.sort(query, User, sort_by, sort_dir)
return database.paginate(query, page, count)

View File

@ -28,7 +28,6 @@
"gulp-imagemin": "^0.6.2",
"gulp-inject": "~1.0.1",
"gulp-jshint": "^1.10.0",
"gulp-karma": "^0.0.4",
"gulp-load-plugins": "^0.5.3",
"gulp-minify-html": "~0.1.4",
"gulp-ng-annotate": "~0.5.2",
@ -51,7 +50,9 @@
"main-bower-files": "^1.0.2",
"require-dir": "~0.3.0",
"streamqueue": "^0.1.1",
"uglify-save-license": "^0.4.1"
"uglify-save-license": "^0.4.1",
"karma": "~0.13.2",
"bower": "~1.4.1"
},
"engines": {
"node": ">=0.10.0"
@ -59,9 +60,13 @@
"scripts": {
"postinstall": "bower install --allow-root",
"pretest": "npm install && npm run build_static",
"build_static": "gulp dist",
"build_static": "gulp build",
"prelint": "npm install",
"lint": "jshint app/",
"lint": "jshint lemur/static/app/",
"test": "gulp test"
},
"devDependencies": {
"jshint": "^2.8.0",
"karma-chrome-launcher": "^0.2.0"
}
}

12
setup.cfg Normal file
View File

@ -0,0 +1,12 @@
[pytest]
python_files=test*.py
addopts=--tb=native -p no:doctest
norecursedirs=bin dist docs htmlcov script hooks node_modules .* {args}
[flake8]
ignore = F999,E501,E128,E124,E402,W503,E731,F841
max-line-length = 100
exclude = .tox,.git,*/migrations/*,lemur/static/*,docs/*
[wheel]
universal = 1

View File

@ -14,6 +14,7 @@ import os.path
from distutils import log
from distutils.core import Command
from setuptools.command.develop import develop
from setuptools.command.install import install
from setuptools.command.sdist import sdist
from setuptools import setup
from subprocess import check_output
@ -37,7 +38,7 @@ install_requires=[
'six>=1.9.0',
'gunicorn>=19.3.0',
'pycrypto>=2.6.1',
'cryptography>=0.9',
'cryptography>=1.0dev',
'pyopenssl>=0.15.1',
'pyjwt>=1.0.1',
'xmltodict>=0.9.2'
@ -45,10 +46,10 @@ install_requires=[
tests_require = [
'pyflakes',
'moto',
'nose',
'pytest',
'pytest-flask'
'moto==0.4.6',
'nose==1.3.7',
'pytest==2.7.2',
'pytest-flask==0.8.1'
]
docs_require = [
@ -56,6 +57,26 @@ docs_require = [
'sphinxcontrib-httpdomain'
]
dev_requires = [
'flake8>=2.0,<2.1',
]
class SmartInstall(install):
"""
Installs Lemur into the Python environment.
If the package indicator is missing, this will also force a run of
`build_static` which is required for JavaScript assets and other things.
"""
def _needs_static(self):
return not os.path.exists(os.path.join(ROOT, 'lemur-package.json'))
def run(self):
if self._needs_static():
self.run_command('build_static')
install.run(self)
class DevelopWithBuildStatic(develop):
def install_for_development(self):
self.run_command('build_static')
@ -79,7 +100,7 @@ class BuildStatic(Command):
log.info("running [npm install --quiet]")
check_output(['npm', 'install', '--quiet'], cwd=ROOT)
log.info("running [gulp buld]")
log.info("running [gulp build]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'build'], cwd=ROOT)
log.info("running [gulp package]")
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT)
@ -96,12 +117,15 @@ setup(
install_requires=install_requires,
extras_require={
'tests': tests_require,
'docs': docs_require
'docs': docs_require,
'dev': dev_requires,
},
cmdclass={
'build_static': BuildStatic,
'develop': DevelopWithBuildStatic,
'sdist': SdistWithBuildStatic
'sdist': SdistWithBuildStatic,
'install': SmartInstall
},
entry_points={
'console_scripts': [