Merge pull request #20 from kevgliss/destinations

Adds support for 'Destinations'
This commit is contained in:
kevgliss 2015-07-10 17:20:05 -07:00
commit 14b62a145a
76 changed files with 1498 additions and 1312 deletions

View File

@ -15,11 +15,12 @@ from lemur.roles.views import mod as roles_bp
from lemur.auth.views import mod as auth_bp
from lemur.domains.views import mod as domains_bp
from lemur.elbs.views import mod as elbs_bp
from lemur.accounts.views import mod as accounts_bp
from lemur.destinations.views import mod as destinations_bp
from lemur.authorities.views import mod as authorities_bp
from lemur.listeners.views import mod as listeners_bp
from lemur.certificates.views import mod as certificates_bp
from lemur.status.views import mod as status_bp
from lemur.plugins.views import mod as plugins_bp
LEMUR_BLUEPRINTS = (
users_bp,
@ -27,11 +28,12 @@ LEMUR_BLUEPRINTS = (
auth_bp,
domains_bp,
elbs_bp,
accounts_bp,
destinations_bp,
authorities_bp,
listeners_bp,
certificates_bp,
status_bp
status_bp,
plugins_bp,
)
def create_app(config=None):

View File

@ -1,29 +0,0 @@
"""
.. module: lemur.accounts.models
: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>
"""
from sqlalchemy import Column, Integer, String, Text
from sqlalchemy.orm import relationship
from lemur.database import db
class Account(db.Model):
__tablename__ = 'accounts'
id = Column(Integer, primary_key=True)
account_number = Column(String(32), unique=True)
label = Column(String(32))
notes = Column(Text())
elbs = relationship("ELB", backref='account', cascade="all, delete, delete-orphan")
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
def serialize(self):
blob = self.as_dict()
blob['elbs'] = [x.id for x in self.elbs]
return blob

View File

@ -1,112 +0,0 @@
"""
.. module: lemur.accounts.views
: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>
"""
from lemur import database
from lemur.accounts.models import Account
from lemur.certificates.models import Certificate
def create(account_number, label=None, comments=None):
"""
Creates a new account, that can then be used as a destination for certificates.
:param account_number: AWS assigned ID
:param label: Account common name
:param comments:
:rtype : Account
:return: New account
"""
acct = Account(account_number=account_number, label=label, notes=comments)
return database.create(acct)
def update(account_id, account_number, label, comments=None):
"""
Updates an existing account.
:param account_id: Lemur assigned ID
:param account_number: AWS assigned ID
:param label: Account common name
:param comments:
:rtype : Account
:return:
"""
account = get(account_id)
account.account_number = account_number
account.label = label
account.notes = comments
return database.update(account)
def delete(account_id):
"""
Deletes an account.
:param account_id: Lemur assigned ID
"""
database.delete(get(account_id))
def get(account_id):
"""
Retrieves an account by it's lemur assigned ID.
:param account_id: Lemur assigned ID
:rtype : Account
:return:
"""
return database.get(Account, account_id)
def get_by_account_number(account_number):
"""
Retrieves an account by it's amazon assigned ID.
:rtype : Account
:param account_number: AWS assigned ID
:return:
"""
return database.get(Account, account_number, field='account_number')
def get_all():
"""
Retrieves all account currently known by Lemur.
:return:
"""
query = database.session_query(Account)
return database.find_all(query, Account, {}).all()
def render(args):
sort_by = args.pop('sort_by')
sort_dir = args.pop('sort_dir')
page = args.pop('page')
count = args.pop('count')
filt = args.pop('filter')
certificate_id = args.pop('certificate_id', None)
if certificate_id:
query = database.session_query(Account).join(Certificate, Account.certificate)
query = query.filter(Certificate.id == certificate_id)
else:
query = database.session_query(Account)
if filt:
terms = filt.split(';')
query = database.filter(query, Account, terms)
query = database.find_all(query, Account, args)
if sort_by and sort_dir:
query = database.sort(query, Account, sort_by, sort_dir)
return database.paginate(query, page, count)

View File

@ -54,7 +54,7 @@ def create(kwargs):
kwargs['creator'] = g.current_user.email
cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs)
cert = cert_service.save_cert(cert_body, None, intermediate, None)
cert = cert_service.save_cert(cert_body, None, intermediate, [])
cert.user = g.current_user
# we create and attach any roles that the issuer gives us

View File

@ -22,7 +22,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.models import certificate_associations, certificate_account_associations
from lemur.models import certificate_associations, certificate_destination_associations
def create_name(issuer, not_before, not_after, subject, san):
@ -215,7 +215,7 @@ class Certificate(db.Model):
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'))
authority_id = Column(Integer, ForeignKey('authorities.id'))
accounts = relationship("Account", secondary=certificate_account_associations, backref='certificate')
accounts = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate')

View File

@ -13,12 +13,10 @@ from sqlalchemy import func, or_
from flask import g, current_app
from lemur import database
from lemur.common.services.aws import iam
from lemur.plugins.base import plugins
from lemur.certificates.models import Certificate
from lemur.accounts.models import Account
from lemur.accounts import service as account_service
from lemur.destinations.models import Destination
from lemur.authorities.models import Authority
from lemur.roles.models import Role
@ -59,28 +57,6 @@ def delete(cert_id):
database.delete(get(cert_id))
def disassociate_aws_account(certs, account):
"""
Removes the account association from a certificate. We treat AWS as a completely
external service. Certificates are added and removed from this service but a record
of that certificate is always kept and tracked by Lemur. This allows us to migrate
certificates to different accounts with ease.
:param certs:
:param account:
"""
account_certs = Certificate.query.filter(Certificate.accounts.any(Account.id == 1)).\
filter(~Certificate.body.in_(certs)).all()
for a_cert in account_certs:
try:
a_cert.accounts.remove(account)
except Exception as e:
current_app.logger.debug("Skipping {0} account {1} is already disassociated".format(a_cert.name, account.label))
continue
database.update(a_cert)
def get_all_certs():
"""
Retrieves all certificates within Lemur.
@ -134,7 +110,7 @@ def mint(issuer_options):
issuer_options['creator'] = g.user.email
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('accounts'))
cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('destinations'))
cert.user = g.user
cert.authority = authority
database.update(cert)
@ -154,9 +130,10 @@ def import_certificate(**kwargs):
:param kwargs:
"""
from lemur.users import service as user_service
cert = Certificate(kwargs['public_certificate'])
cert.owner = kwargs.get('owner', )
cert.creator = kwargs.get('creator', 'Lemur')
cert.owner = kwargs.get('owner', current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL'))
cert.creator = kwargs.get('creator', user_service.get_by_email('lemur@nobody'))
# NOTE existing certs may not follow our naming standard we will
# overwrite the generated name with the actual cert name
@ -166,31 +143,29 @@ def import_certificate(**kwargs):
if kwargs.get('user'):
cert.user = kwargs.get('user')
if kwargs.get('account'):
cert.accounts.append(kwargs.get('account'))
if kwargs.get('destination'):
cert.destinations.append(kwargs.get('destination'))
cert = database.create(cert)
return cert
def save_cert(cert_body, private_key, cert_chain, accounts):
def save_cert(cert_body, private_key, cert_chain, destinations):
"""
Determines if the certificate needs to be uploaded to AWS or other services.
:param cert_body:
:param private_key:
:param cert_chain:
:param challenge:
:param csr_config:
:param accounts:
:param destinations:
"""
cert = Certificate(cert_body, private_key, cert_chain)
# if we have an AWS accounts lets upload them
if accounts:
for account in accounts:
account = account_service.get(account['id'])
iam.upload_cert(account.account_number, cert, private_key, cert_chain)
cert.accounts.append(account)
# we should save them to any destination that is requested
for destination in destinations:
destination_plugin = plugins.get(destination['plugin']['slug'])
destination_plugin.upload(cert, private_key, cert_chain, destination['plugin']['pluginOptions'])
return cert
@ -198,13 +173,11 @@ def upload(**kwargs):
"""
Allows for pre-made certificates to be imported into Lemur.
"""
# save this cert the same way we save all of our certs, including uploading
# to aws if necessary
cert = save_cert(
kwargs.get('public_cert'),
kwargs.get('private_key'),
kwargs.get('intermediate_cert'),
kwargs.get('accounts')
kwargs.get('destinations')
)
cert.owner = kwargs['owner']
@ -237,7 +210,7 @@ def render(args):
query = database.session_query(Certificate)
time_range = args.pop('time_range')
account_id = args.pop('account_id')
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
@ -260,8 +233,8 @@ def render(args):
)
return database.sort_and_page(query, Certificate, args)
if 'account' in terms:
query = query.filter(Certificate.accounts.any(Account.id == terms[1]))
if 'destination' in terms:
query = query.filter(Certificate.destinations.any(Destination.id == terms[1]))
elif 'active' in filt: # this is really weird but strcmp seems to not work here??
query = query.filter(Certificate.active == terms[1])
else:
@ -276,8 +249,8 @@ def render(args):
)
)
if account_id:
query = query.filter(Certificate.accounts.any(Account.id == account_id))
if destination_id:
query = query.filter(Certificate.destinations.any(Destination.id == destination_id))
if time_range:
to = arrow.now().replace(weeks=+time_range).format('YYYY-MM-DD')
@ -404,8 +377,8 @@ def stats(**kwargs):
if kwargs.get('active') == 'true':
query = query.filter(Certificate.elb_listeners.any())
if kwargs.get('account_id'):
query = query.filter(Certificate.accounts.any(Account.id == kwargs.get('account_id')))
if kwargs.get('destination_id'):
query = query.filter(Certificate.destinations.any(Destination.id == kwargs.get('destination_id')))
if kwargs.get('metric') == 'not_after':
start = arrow.utcnow()

View File

@ -7,10 +7,6 @@
to 'sync' with as many different datasources as possible to try and track
any certificate that may be in use.
This include querying AWS for certificates attached to ELBs, querying our own
internal CA for certificates issued. As well as some rudimentary source code
scraping that attempts to find certificates checked into source code.
These operations are typically run on a periodic basis from either the command
line or a cron job.
@ -18,151 +14,33 @@
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import requests
from bs4 import BeautifulSoup
from flask import current_app
from lemur.users import service as user_service
from lemur.accounts import service as account_service
from lemur.certificates import service as cert_service
from lemur.certificates.models import Certificate, get_name_from_arn
from lemur.common.services.aws.iam import get_all_server_certs
from lemur.common.services.aws.iam import get_cert_from_arn
from lemur.plugins.base import plugins
from lemur.plugins.bases.source import SourcePlugin
def aws():
"""
Attempts to retrieve all certificates located in known AWS accounts
:raise e:
"""
def sync():
for plugin in plugins:
new = 0
updated = 0
if isinstance(plugin, SourcePlugin):
if plugin.is_enabled():
current_app.logger.error("Retrieving certificates from {0}".format(plugin.title))
certificates = plugin.get_certificates()
# all certificates 'discovered' by lemur are tracked by the lemur
# user
user = user_service.get_by_email('lemur@nobody')
for certificate in certificates:
exists = cert_service.find_duplicates(certificate)
# we don't need to check regions as IAM is a global service
for account in account_service.get_all():
certificate_bodies = []
try:
cert_arns = get_all_server_certs(account.account_number)
except Exception as e:
current_app.logger.error("Failed to to get Certificates from '{}/{}' reason {}".format(
account.label, account.account_number, e.message)
)
raise e
current_app.logger.info("found {} certs from '{}/{}' ... ".format(
len(cert_arns), account.account_number, account.label)
)
for cert in cert_arns:
cert_body = get_cert_from_arn(cert.arn)[0]
certificate_bodies.append(cert_body)
existing = cert_service.find_duplicates(cert_body)
if not existing:
cert_service.import_certificate(
**{'owner': 'secops@netflix.com',
'creator': 'Lemur',
'name': get_name_from_arn(cert.arn),
'account': account,
'user': user,
'public_certificate': cert_body
}
)
if not exists:
cert_service.import_certificate(**certificate)
new += 1
elif len(existing) == 1: # we check to make sure we know about the current account for this certificate
for e_account in existing[0].accounts:
if e_account.account_number == account.account_number:
break
else: # we have a new account
existing[0].accounts.append(account)
if len(exists) == 1:
updated += 1
else:
current_app.logger.error(
"Multiple certificates with the same body found, unable to correctly determine which entry to update"
)
# TODO associated cert with source
# TODO update cert if found from different source
# TODO dissassociate source if missing
# make sure we remove any certs that have been removed from AWS
cert_service.disassociate_aws_account(certificate_bodies, account)
current_app.logger.info("found {} new certificates in aws {}".format(new, account.label))
def cloudca():
"""
Attempts to retrieve all certificates that are stored in CloudCA
"""
user = user_service.get_by_email('lemur@nobody')
# sync all new certificates/authorities not created through lemur
issuer = plugins.get('cloudca')
authorities = issuer.get_authorities()
total = 0
new = 1
for authority in authorities:
certs = issuer.get_cert(ca_name=authority)
for cert in certs:
total += 1
cert['user'] = user
existing = cert_service.find_duplicates(cert['public_certificate'])
if not existing:
new += 1
try:
cert_service.import_certificate(**cert)
except NameError as e:
current_app.logger.error("Cannot import certificate {0}".format(cert))
current_app.logger.debug("Found {0} total certificates in cloudca".format(total))
current_app.logger.debug("Found {0} new certificates in cloudca".format(new))
def source():
"""
Attempts to track certificates that are stored in Source Code
"""
new = 0
keywords = ['"--- Begin Certificate ---"']
endpoint = current_app.config.get('LEMUR_SOURCE_SEARCH')
maxresults = 25000
current_app.logger.info("Searching {0} for new certificates".format(endpoint))
for keyword in keywords:
current_app.logger.info("Looking for keyword: {0}".format(keyword))
url = "{}/source/s?n={}&start=1&sort=relevancy&q={}&project=github%2Cperforce%2Cstash".format(endpoint, maxresults, keyword)
current_app.logger.debug("Request url: {0}".format(url))
r = requests.get(url, timeout=20)
if r.status_code != 200:
current_app.logger.error("Unable to retrieve: {0} Status Code: {1}".format(url, r.status_code))
continue
soup = BeautifulSoup(r.text, "lxml")
results = soup.find_all(title='Download')
for result in results:
parts = result['href'].split('/')
path = "/".join(parts[:-1])
filename = parts[-1:][0]
r = requests.get("{0}{1}/{2}".format(endpoint, path, filename))
if r.status_code != 200:
current_app.logger.error("Unable to retrieve: {0} Status Code: {1}".format(url, r.status_code))
continue
try:
# validate we have a real certificate
cert = Certificate(r.content)
# do a lookup to see if we know about this certificate
existing = cert_service.find_duplicates(r.content)
if not existing:
current_app.logger.debug(cert.name)
cert_service.import_certificate()
new += 1
except Exception as e:
current_app.logger.debug("Could not parse the following 'certificate': {0} Reason: {1}".format(r.content, e))

View File

@ -164,7 +164,7 @@ class CertificatesList(AuthenticatedResource):
parser.add_argument('owner', type=bool, location='args')
parser.add_argument('id', type=str, location='args')
parser.add_argument('active', type=bool, location='args')
parser.add_argument('accountId', type=int, dest="account_id", location='args')
parser.add_argument('destinationId', type=int, dest="destination_id", location='args')
parser.add_argument('creator', type=str, location='args')
parser.add_argument('show', type=str, location='args')
@ -271,7 +271,7 @@ class CertificatesList(AuthenticatedResource):
:statuscode 403: unauthenticated
"""
self.reqparse.add_argument('extensions', type=dict, location='json')
self.reqparse.add_argument('accounts', type=list, location='json')
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
@ -330,7 +330,7 @@ class CertificatesUpload(AuthenticatedResource):
"publicCert": "---Begin Public...",
"intermediateCert": "---Begin Public...",
"privateKey": "---Begin Private..."
"accounts": []
"destinations": []
}
**Example response**:
@ -364,19 +364,19 @@ class CertificatesUpload(AuthenticatedResource):
:arg publicCert: valid PEM public key for certificate
:arg intermediateCert valid PEM intermediate key for certificate
:arg privateKey: valid PEM private key for certificate
:arg accounts: list of aws accounts to upload the certificate to
:arg destinations: list of aws destinations to upload the certificate to
:reqheader Authorization: OAuth token to authenticate
:statuscode 403: unauthenticated
:statuscode 200: no error
"""
self.reqparse.add_argument('owner', type=str, required=True, location='json')
self.reqparse.add_argument('publicCert', type=pem_str, required=True, dest='public_cert', location='json')
self.reqparse.add_argument('accounts', type=list, dest='accounts', location='json')
self.reqparse.add_argument('destinations', type=list, default=[], dest='destinations', location='json')
self.reqparse.add_argument('intermediateCert', type=pem_str, dest='intermediate_cert', location='json')
self.reqparse.add_argument('privateKey', type=private_key_str, dest='private_key', location='json')
args = self.reqparse.parse_args()
if args.get('accounts'):
if args.get('destinations'):
if args.get('private_key'):
return service.upload(**args)
else:
@ -393,7 +393,7 @@ class CertificatesStats(AuthenticatedResource):
def get(self):
self.reqparse.add_argument('metric', type=str, location='args')
self.reqparse.add_argument('range', default=32, type=int, location='args')
self.reqparse.add_argument('accountId', dest='account_id', location='args')
self.reqparse.add_argument('destinationId', dest='destination_id', location='args')
self.reqparse.add_argument('active', type=str, default='true', location='args')
args = self.reqparse.parse_args()

View File

@ -41,7 +41,7 @@ class marshal_items(object):
return {'items': _filter_items(resp.items), 'total': resp.total}
if isinstance(resp, list):
return _filter_items(resp)
return {'items': _filter_items(resp), 'total': len(resp)}
return marshal(resp, self.fields)
except Exception as e:

View File

@ -0,0 +1,28 @@
"""
.. module: lemur.destinations.models
: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 copy
from sqlalchemy import Column, Integer, String, Text
from sqlalchemy_utils import JSONType
from lemur.database import db
from lemur.plugins.base import plugins
class Destination(db.Model):
__tablename__ = 'destinations'
id = Column(Integer, primary_key=True)
label = Column(String(32))
options = Column(JSONType)
description = Column(Text())
plugin_name = Column(String(32))
@property
def plugin(self):
p = plugins.get(self.plugin_name)
c = copy.deepcopy(p)
c.options = self.options
return c

View File

@ -0,0 +1,110 @@
"""
.. module: lemur.destinations.service
: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>
"""
from lemur import database
from lemur.destinations.models import Destination
from lemur.certificates.models import Certificate
def create(label, plugin_name, options, description=None):
"""
Creates a new destination, that can then be used as a destination for certificates.
:param label: Destination common name
:param description:
:rtype : Destination
:return: New destination
"""
destination = Destination(label=label, options=options, plugin_name=plugin_name, description=description)
return database.create(destination)
def update(destination_id, label, options, description):
"""
Updates an existing destination.
:param destination_id: Lemur assigned ID
:param destination_number: AWS assigned ID
:param label: Destination common name
:param comments:
:rtype : Destination
:return:
"""
destination = get(destination_id)
destination.label = label
description.options = options
destination.description = description
return database.update(destination)
def delete(destination_id):
"""
Deletes an destination.
:param destination_id: Lemur assigned ID
"""
database.delete(get(destination_id))
def get(destination_id):
"""
Retrieves an destination by it's lemur assigned ID.
:param destination_id: Lemur assigned ID
:rtype : Destination
:return:
"""
return database.get(Destination, destination_id)
def get_by_label(label):
"""
Retrieves a destination by it's label
:param label:
:return:
"""
return database.get(Destination, label, field='label')
def get_all():
"""
Retrieves all destination currently known by Lemur.
:return:
"""
query = database.session_query(Destination)
return database.find_all(query, Destination, {}).all()
def render(args):
sort_by = args.pop('sort_by')
sort_dir = args.pop('sort_dir')
page = args.pop('page')
count = args.pop('count')
filt = args.pop('filter')
certificate_id = args.pop('certificate_id', None)
if certificate_id:
query = database.session_query(Destination).join(Certificate, Destination.certificate)
query = query.filter(Certificate.id == certificate_id)
else:
query = database.session_query(Destination)
if filt:
terms = filt.split(';')
query = database.filter(query, Destination, terms)
query = database.find_all(query, Destination, args)
if sort_by and sort_dir:
query = database.sort(query, Destination, sort_by, sort_dir)
return database.paginate(query, page, count)

View File

@ -1,5 +1,5 @@
"""
.. module: lemur.accounts.views
.. module: lemur.destinations.views
:platform: Unix
:synopsis: This module contains all of the accounts view code.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
@ -8,35 +8,36 @@
"""
from flask import Blueprint
from flask.ext.restful import Api, reqparse, fields
from lemur.accounts import service
from lemur.destinations import service
from lemur.auth.service import AuthenticatedResource
from lemur.auth.permissions import admin_permission
from lemur.common.utils import paginated_parser, marshal_items
from lemur.plugins.views import FIELDS as PLUGIN_FIELDS
mod = Blueprint('accounts', __name__)
mod = Blueprint('destinations', __name__)
api = Api(mod)
FIELDS = {
'accountNumber': fields.Integer(attribute='account_number'),
'description': fields.String,
'plugin': fields.Nested(PLUGIN_FIELDS, attribute='plugin'),
'label': fields.String,
'comments': fields.String(attribute='notes'),
'id': fields.Integer,
}
class AccountsList(AuthenticatedResource):
""" Defines the 'accounts' endpoint """
class DestinationsList(AuthenticatedResource):
""" Defines the 'destinations' endpoint """
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(AccountsList, self).__init__()
super(DestinationsList, self).__init__()
@marshal_items(FIELDS)
def get(self):
"""
.. http:get:: /accounts
.. http:get:: /destinations
The current account list
@ -44,7 +45,7 @@ class AccountsList(AuthenticatedResource):
.. sourcecode:: http
GET /accounts HTTP/1.1
GET /destinations HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
@ -90,7 +91,7 @@ class AccountsList(AuthenticatedResource):
@marshal_items(FIELDS)
def post(self):
"""
.. http:post:: /accounts
.. http:post:: /destinations
Creates a new account
@ -98,7 +99,7 @@ class AccountsList(AuthenticatedResource):
.. sourcecode:: http
POST /accounts HTTP/1.1
POST /destinations HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
@ -129,23 +130,23 @@ class AccountsList(AuthenticatedResource):
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
self.reqparse.add_argument('accountNumber', type=int, dest="account_number", location='json', required=True)
self.reqparse.add_argument('label', type=str, location='json', required=True)
self.reqparse.add_argument('comments', type=str, location='json')
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
self.reqparse.add_argument('description', type=str, location='json')
args = self.reqparse.parse_args()
return service.create(args['account_number'], args['label'], args['comments'])
return service.create(args['label'], args['plugin']['slug'], args['plugin']['pluginOptions'], args['description'])
class Accounts(AuthenticatedResource):
class Destinations(AuthenticatedResource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(Accounts, self).__init__()
super(Destinations, self).__init__()
@marshal_items(FIELDS)
def get(self, account_id):
def get(self, destination_id):
"""
.. http:get:: /accounts/1
.. http:get:: /destinations/1
Get a specific account
@ -153,7 +154,7 @@ class Accounts(AuthenticatedResource):
.. sourcecode:: http
GET /accounts/1 HTTP/1.1
GET /destinations/1 HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
@ -175,13 +176,13 @@ class Accounts(AuthenticatedResource):
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
return service.get(account_id)
return service.get(destination_id)
@admin_permission.require(http_exception=403)
@marshal_items(FIELDS)
def put(self, account_id):
def put(self, destination_id):
"""
.. http:put:: /accounts/1
.. http:put:: /destinations/1
Updates an account
@ -189,15 +190,10 @@ class Accounts(AuthenticatedResource):
.. sourcecode:: http
POST /accounts/1 HTTP/1.1
POST /destinations/1 HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
{
"accountNumber": 11111111111,
"label": "labelChanged,
"comments": "this is a thing"
}
**Example response**:
@ -220,29 +216,29 @@ class Accounts(AuthenticatedResource):
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
self.reqparse.add_argument('accountNumber', type=int, dest="account_number", location='json', required=True)
self.reqparse.add_argument('label', type=str, location='json', required=True)
self.reqparse.add_argument('comments', type=str, location='json')
self.reqparse.add_argument('pluginOptions', type=dict, location='json', required=True)
self.reqparse.add_argument('description', type=str, location='json')
args = self.reqparse.parse_args()
return service.update(account_id, args['account_number'], args['label'], args['comments'])
return service.update(destination_id, args['label'], args['options'], args['description'])
@admin_permission.require(http_exception=403)
def delete(self, account_id):
service.delete(account_id)
def delete(self, destination_id):
service.delete(destination_id)
return {'result': True}
class CertificateAccounts(AuthenticatedResource):
""" Defines the 'certificate/<int:certificate_id/accounts'' endpoint """
class CertificateDestinations(AuthenticatedResource):
""" Defines the 'certificate/<int:certificate_id/destinations'' endpoint """
def __init__(self):
super(CertificateAccounts, self).__init__()
super(CertificateDestinations, self).__init__()
@marshal_items(FIELDS)
def get(self, certificate_id):
"""
.. http:get:: /certificates/1/accounts
.. http:get:: /certificates/1/destinations
The current account list for a given certificates
@ -250,7 +246,7 @@ class CertificateAccounts(AuthenticatedResource):
.. sourcecode:: http
GET /certificates/1/accounts HTTP/1.1
GET /certificates/1/destinations HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
@ -262,24 +258,6 @@ class CertificateAccounts(AuthenticatedResource):
Vary: Accept
Content-Type: text/javascript
{
"items": [
{
"id": 2,
"accountNumber": 222222222,
"label": "account2",
"comments": "this is a thing"
},
{
"id": 1,
"accountNumber": 11111111111,
"label": "account1",
"comments": "this is a thing"
},
]
"total": 2
}
:query sortBy: field to sort on
:query sortDir: acs or desc
:query page: int. default is 1
@ -294,7 +272,7 @@ class CertificateAccounts(AuthenticatedResource):
return service.render(args)
api.add_resource(AccountsList, '/accounts', endpoint='accounts')
api.add_resource(Accounts, '/accounts/<int:account_id>', endpoint='account')
api.add_resource(CertificateAccounts, '/certificates/<int:certificate_id>/accounts', endpoint='certificateAccounts')
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')

View File

@ -16,7 +16,7 @@ from lemur.listeners.models import Listener
class ELB(db.Model):
__tablename__ = 'elbs'
id = Column(BigInteger, primary_key=True)
account_id = Column(BigInteger, ForeignKey("accounts.id"), index=True)
#account_id = Column(BigInteger, ForeignKey("accounts.id"), index=True)
region = Column(String(32))
name = Column(String(128))
vpc_id = Column(String(128))

View File

@ -12,9 +12,9 @@
"""
from flask import current_app
from lemur.accounts import service as account_service
#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
#from lemur.common.services.aws.elb import get_all_elbs, get_all_regions
def create_new(known, aws, account):

View File

@ -18,7 +18,7 @@ from lemur.listeners.models import Listener
from lemur.elbs import service as elb_service
from lemur.certificates import service as certificate_service
from lemur.common.services.aws.elb import update_listeners, create_new_listeners, delete_listeners
#from lemur.common.services.aws.elb import update_listeners, create_new_listeners, delete_listeners
def verify_attachment(certificate_id, elb_account_number):

View File

@ -13,9 +13,11 @@ from flask_script.commands import ShowUrls, Clean, Server
from lemur import database
from lemur.users import service as user_service
from lemur.roles import service as role_service
from lemur.accounts import service as account_service
from lemur.destinations import service as destination_service
from lemur.certificates import service as cert_service
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
@ -27,7 +29,7 @@ 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.accounts.models import Account
from lemur.destinations.models import Destination
from lemur.domains.models import Domain
from lemur.elbs.models import ELB
from lemur.listeners.models import Listener
@ -96,12 +98,12 @@ SQLALCHEMY_DATABASE_URI = ''
## AWS ##
#########
# Lemur will need STS assume role access to every account you want to monitor
# Lemur will need STS assume role access to every destination you want to monitor
#AWS_ACCOUNT_MAPPINGS = {{
# '1111111111': 'myawsacount'
#}}
## This is useful if you know you only want to monitor one account
## This is useful if you know you only want to monitor one destination
#AWS_REGIONS = ['us-east-1']
#LEMUR_INSTANCE_PROFILE = 'Lemur'
@ -133,6 +135,11 @@ def create():
stamp(revision='head')
@MigrateCommand.command
def drop_all():
database.db.drop_all()
@manager.command
def check_revoked():
"""
@ -227,7 +234,7 @@ class Sync(Command):
class InitializeApp(Command):
"""
This command will bootstrap our database with any accounts as
This command will bootstrap our database with any destinations as
specified by our config.
Additionally a Lemur user will be created as a default user
@ -262,15 +269,20 @@ class InitializeApp(Command):
sys.stdout.write("[-] Default user has already been created, skipping...!\n")
if current_app.config.get('AWS_ACCOUNT_MAPPINGS'):
if plugins.get('aws-destination'):
for account_name, account_number in current_app.config.get('AWS_ACCOUNT_MAPPINGS').items():
account = account_service.get_by_account_number(account_number)
if not account:
account_service.create(account_number, label=account_name)
sys.stdout.write("[+] Added new account {0}:{1}!\n".format(account_number, account_name))
destination = destination_service.get_by_label(account_name)
options = dict(account_number=account_number)
if not destination:
destination_service.create(account_name, 'aws-destination', options,
description="This is an auto-generated AWS destination.")
sys.stdout.write("[+] Added new destination {0}:{1}!\n".format(account_number, account_name))
else:
sys.stdout.write("[-] Account already exists, skipping...!\n")
else:
sys.stdout.write("[!] Skipping adding AWS destinations AWS plugin no available\n")
sys.stdout.write("[/] Done!\n")

View File

@ -18,8 +18,8 @@ certificate_associations = db.Table('certificate_associations',
Column('certificate_id', Integer, ForeignKey('certificates.id'))
)
certificate_account_associations = db.Table('certificate_account_associations',
Column('account_id', Integer, ForeignKey('accounts.id', ondelete='cascade')),
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'))
)

View File

@ -8,7 +8,6 @@
from flask import current_app
from lemur.common.managers import InstanceManager
# inspired by https://github.com/getsentry/sentry
class PluginManager(InstanceManager):
def __iter__(self):
@ -17,8 +16,10 @@ class PluginManager(InstanceManager):
def __len__(self):
return sum(1 for i in self.all())
def all(self, version=1):
def all(self, version=1, plugin_type=None):
for plugin in sorted(super(PluginManager, self).all(), key=lambda x: x.get_title()):
if not plugin.type == plugin_type and plugin_type:
continue
if not plugin.is_enabled():
continue
if version is not None and plugin.__version__ != version:

View File

@ -47,12 +47,13 @@ class IPlugin(local):
# Configuration specifics
conf_key = None
conf_title = None
options = {}
# Global enabled state
enabled = True
can_disable = True
def is_enabled(self, project=None):
def is_enabled(self):
"""
Returns a boolean representing if this plugin is enabled.
If ``project`` is passed, it will limit the scope to that project.

View File

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

View File

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

View File

@ -13,6 +13,8 @@ class IssuerPlugin(Plugin):
This is the base class from which all of the supported
issuers will inherit from.
"""
type = 'issuer'
def create_certificate(self):
raise NotImplementedError

View File

@ -0,0 +1,19 @@
"""
.. module: lemur.bases.source
: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>
"""
from lemur.plugins.base import Plugin
class SourcePlugin(Plugin):
type = 'source'
def get_certificates(self):
raise NotImplemented
def get_options(self):
return {}

View File

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

View File

@ -10,7 +10,7 @@ import boto.ec2
from flask import current_app
from lemur.exceptions import InvalidListener
from lemur.common.services.aws.sts import assume_service
from lemur.plugins.lemur_aws.sts import assume_service
def is_valid(listener_tuple):

View File

@ -6,8 +6,17 @@
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import current_app
from lemur.common.services.aws.sts import assume_service
from lemur.plugins.lemur_aws.sts import assume_service
def get_name_from_arn(arn):
"""
Extract the certificate name from an arn.
:param arn: IAM SSL arn
:return: name of the certificate as uploaded to AWS
"""
return arn.split("/", 1)[1]
def ssl_split(param_string):

View File

@ -0,0 +1,77 @@
"""
.. module: lemur.plugins.lemur_aws.aws
: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>
"""
from lemur.plugins.bases import DestinationPlugin, SourcePlugin
from lemur.plugins.lemur_aws import iam, elb
from lemur.plugins import lemur_aws as aws
def find_value(name, options):
for o in options:
if o.get(name):
return o['value']
class AWSDestinationPlugin(DestinationPlugin):
title = 'AWS'
slug = 'aws-destination'
description = 'Allow the uploading of certificates to AWS IAM'
version = aws.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
options = [
{
'name': 'accountNumber',
'type': 'int',
'required': True,
'validation': '/^[0-9]{12,12}$/',
'helpMessage': 'Must be a valid AWS account number!',
}
]
#'elb': {
# 'name': {'type': 'name'},
# 'region': {'type': 'str'},
# 'port': {'type': 'int'}
#}
def upload(self, cert, private_key, cert_chain, options, **kwargs):
iam.upload_cert(find_value('accountNumber', options), cert, private_key, cert_chain=cert_chain)
e = find_value('elb', options)
if e:
elb.attach_certificate(kwargs['accountNumber'], ['region'], e['name'], e['port'], e['certificateId'])
class AWSSourcePlugin(SourcePlugin):
title = 'AWS'
slug = 'aws-source'
description = 'Discovers all SSL certificates in an AWS account'
version = aws.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
options = {
'accountNumber': {'type': 'int'},
'pollRate': {'type': 'int', 'default': '60'}
}
def get_certificates(self, **kwargs):
certs = []
arns = elb.get_all_server_certs(kwargs['account_number'])
for arn in arns:
cert_body = iam.get_cert_from_arn(arn)
cert_name = iam.get_name_from_arn(arn)
cert = dict(
public_certificate=cert_body,
name=cert_name
)
certs.append(cert)
return certs

View File

@ -18,12 +18,12 @@ from requests.adapters import HTTPAdapter
from flask import current_app
from lemur.exceptions import LemurException
from lemur.plugins.bases import IssuerPlugin
from lemur.plugins.bases import IssuerPlugin, SourcePlugin
from lemur.plugins import lemur_cloudca as cloudca
from lemur.authorities import service as authority_service
API_ENDPOINT = '/v1/ca/netflix'
API_ENDPOINT = '/v1/ca/netflix' # TODO this should be configurable
class CloudCAException(LemurException):
@ -142,15 +142,7 @@ def get_auth_data(ca_name):
raise CloudCAException("You do not have the required role to issue certificates from {0}".format(ca_name))
class CloudCAPlugin(IssuerPlugin):
title = 'CloudCA'
slug = 'cloudca'
description = 'Enables the creation of certificates from the cloudca API.'
version = cloudca.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
class CloudCA(object):
def __init__(self, *args, **kwargs):
self.session = requests.Session()
self.session.mount('https://', CloudCAHostNameCheckingAdapter())
@ -162,7 +154,69 @@ class CloudCAPlugin(IssuerPlugin):
else:
current_app.logger.warning("No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA")
super(CloudCAPlugin, self).__init__(*args, **kwargs)
super(CloudCA, self).__init__(*args, **kwargs)
def post(self, endpoint, data):
"""
HTTP POST to CloudCA
:param endpoint:
:param data:
:return:
"""
data = dumps(dict(data.items() + get_auth_data(data['caName']).items()))
# we set a low timeout, if cloudca is down it shouldn't bring down
# lemur
response = self.session.post(self.url + endpoint, data=data, timeout=10, verify=self.ca_bundle)
return process_response(response)
def get(self, endpoint):
"""
HTTP GET to CloudCA
:param endpoint:
:return:
"""
response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle)
return process_response(response)
def random(self, length=10):
"""
Uses CloudCA as a decent source of randomness.
:param length:
:return:
"""
endpoint = '/v1/random/{0}'.format(length)
response = self.session.get(self.url + endpoint, verify=self.ca_bundle)
return response
def get_authorities(self):
"""
Retrieves authorities that were made outside of Lemur.
:return:
"""
endpoint = '{0}/listCAs'.format(API_ENDPOINT)
authorities = []
for ca in self.get(endpoint)['data']['caList']:
try:
authorities.append(ca['caName'])
except AttributeError as e:
current_app.logger.error("No authority has been defined for {}".format(ca['caName']))
return authorities
class CloudCAIssuerPlugin(IssuerPlugin, CloudCA):
title = 'CloudCA'
slug = 'cloudca-issuer'
description = 'Enables the creation of certificates from the cloudca API.'
version = cloudca.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
def create_authority(self, options):
"""
@ -205,22 +259,6 @@ class CloudCAPlugin(IssuerPlugin):
return cert, "".join(intermediates), roles,
def get_authorities(self):
"""
Retrieves authorities that were made outside of Lemur.
:return:
"""
endpoint = '{0}/listCAs'.format(API_ENDPOINT)
authorities = []
for ca in self.get(endpoint)['data']['caList']:
try:
authorities.append(ca['caName'])
except AttributeError as e:
current_app.logger.error("No authority has been defined for {}".format(ca['caName']))
return authorities
def create_certificate(self, csr, options):
"""
Creates a new certificate from cloudca
@ -259,16 +297,25 @@ class CloudCAPlugin(IssuerPlugin):
return cert, "".join(intermediates),
def random(self, length=10):
"""
Uses CloudCA as a decent source of randomness.
:param length:
:return:
"""
endpoint = '/v1/random/{0}'.format(length)
response = self.session.get(self.url + endpoint, verify=self.ca_bundle)
return response
class CloudCASourcePlugin(SourcePlugin, CloudCA):
title = 'CloudCA'
slug = 'cloudca-source'
description = 'Discovers all SSL certificates in CloudCA'
version = cloudca.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
options = {
'pollRate': {'type': 'int', 'default': '60'}
}
def get_certificates(self, **kwargs):
certs = []
for authority in self.get_authorities():
certs += self.get_cert(ca_name=authority)
return
def get_cert(self, ca_name=None, cert_handle=None):
"""
@ -297,29 +344,3 @@ class CloudCAPlugin(IssuerPlugin):
})
return certs
def post(self, endpoint, data):
"""
HTTP POST to CloudCA
:param endpoint:
:param data:
:return:
"""
data = dumps(dict(data.items() + get_auth_data(data['caName']).items()))
# we set a low timeout, if cloudca is down it shouldn't bring down
# lemur
response = self.session.post(self.url + endpoint, data=data, timeout=10, verify=self.ca_bundle)
return process_response(response)
def get(self, endpoint):
"""
HTTP GET to CloudCA
:param endpoint:
:return:
"""
response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle)
return process_response(response)

View File

@ -75,9 +75,9 @@ def handle_response(content):
return d
class VerisignPlugin(IssuerPlugin):
title = 'VeriSign'
slug = 'verisign'
class VerisignIssuerPlugin(IssuerPlugin):
title = 'Verisign'
slug = 'verisign-issuer'
description = 'Enables the creation of certificates by the VICE2.0 verisign API.'
version = verisign.VERSION
@ -87,7 +87,7 @@ class VerisignPlugin(IssuerPlugin):
def __init__(self, *args, **kwargs):
self.session = requests.Session()
self.session.cert = current_app.config.get('VERISIGN_PEM_PATH')
super(VerisignPlugin, self).__init__(*args, **kwargs)
super(VerisignIssuerPlugin, self).__init__(*args, **kwargs)
def create_certificate(self, csr, issuer_options):
"""

140
lemur/plugins/views.py Normal file
View File

@ -0,0 +1,140 @@
"""
.. module: lemur.plugins.views
:platform: Unix
:synopsis: This module contains all of the accounts view code.
: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 Blueprint
from flask.ext.restful import Api, reqparse, fields
from lemur.auth.service import AuthenticatedResource
from lemur.common.utils import marshal_items
from lemur.plugins.base import plugins
mod = Blueprint('plugins', __name__)
api = Api(mod)
FIELDS = {
'title': fields.String,
'pluginOptions': fields.Raw(attribute='options'),
'description': fields.String,
'version': fields.String,
'author': fields.String,
'authorUrl': fields.String,
'type': fields.String,
'slug': fields.String,
}
class PluginsList(AuthenticatedResource):
""" Defines the 'plugins' endpoint """
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(PluginsList, self).__init__()
@marshal_items(FIELDS)
def get(self):
"""
.. http:get:: /plugins
The current plugin list
**Example request**:
.. sourcecode:: http
GET /plugins HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"items": [
{
"id": 2,
"accountNumber": 222222222,
"label": "account2",
"comments": "this is a thing"
},
{
"id": 1,
"accountNumber": 11111111111,
"label": "account1",
"comments": "this is a thing"
},
]
"total": 2
}
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
return plugins.all()
class PluginsTypeList(AuthenticatedResource):
""" Defines the 'plugins' endpoint """
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(PluginsTypeList, self).__init__()
@marshal_items(FIELDS)
def get(self, plugin_type):
"""
.. http:get:: /plugins/issuer
The current plugin list
**Example request**:
.. sourcecode:: http
GET /plugins/issuer HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"items": [
{
"id": 2,
"accountNumber": 222222222,
"label": "account2",
"comments": "this is a thing"
},
{
"id": 1,
"accountNumber": 11111111111,
"label": "account1",
"comments": "this is a thing"
},
]
"total": 2
}
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
"""
return list(plugins.all(plugin_type=plugin_type))
api.add_resource(PluginsList, '/plugins', endpoint='plugins')
api.add_resource(PluginsTypeList, '/plugins/<plugin_type>', endpoint='pluginType')

View File

@ -1,27 +0,0 @@
'use strict';
angular.module('lemur')
.config(function config($routeProvider) {
$routeProvider.when('/accounts/create', {
templateUrl: '/angular/accounts/account/account.tpl.html',
controller: 'AccountsCreateController'
});
$routeProvider.when('/accounts/:id/edit', {
templateUrl: '/angular/accounts/account/account.tpl.html',
controller: 'AccountsEditController'
});
})
.controller('AccountsCreateController', function ($scope, AccountService, LemurRestangular){
$scope.account = LemurRestangular.restangularizeElement(null, {}, 'accounts');
$scope.save = AccountService.create;
})
.controller('AccountsEditController', function ($scope, $routeParams, AccountService, AccountApi) {
AccountApi.get($routeParams.id).then(function (account) {
$scope.account = account;
});
$scope.save = AccountService.update;
});

View File

@ -1,45 +0,0 @@
<h2 class="featurette-heading"><span ng-show="!account.fromServer">Create</span><span ng-show="account.fromServer">Edit</span> Account <span class="text-muted"><small>next in line please
</small></span></h2>
<div class="panel panel-default">
<div class="panel-heading">
<a href="/#/accounts/" class="btn btn-danger pull-right">Cancel</a>
<div class="clearfix"></div>
</div>
<div class="panel-body">
<form name="createForm" class="form-horizontal" role="form" novalidate>
<div class="form-group"
ng-class="{'has-error': createForm.name.$invalid, 'has-success': !createForm.name.$invalid&&createForm.name.$dirty}">
<label class="control-label col-sm-2">
Name
</label>
<div class="col-sm-10">
<input name="name" ng-model="account.label" placeholder="Name" class="form-control" required/>
<p ng-show="createForm.name.$invalid && !createForm.name.$pristine" class="help-block">You must enter an account name</p>
</div>
</div>
<div class="form-group"
ng-class="{'has-error': createForm.accountNumber.$invalid, 'has-success': !createForm.accountNumber.$invalid&&createForm.accountNumber.$dirty}">
<label class="control-label col-sm-2">
Account Number
</label>
<div class="col-sm-10">
<input type="number" name="accountNumber" ng-model="account.accountNumber" placeholder="111111111111" class="form-control" ng-minlength="12" ng-maxlength="12" required/>
<p ng-show="createForm.accountNumber.$invalid && !createForm.accountNumber.$pristine" class="help-block">You must enter an account number</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">
Comments
</label>
<div class="col-sm-10">
<textarea name="comments" ng-model="account.comments" placeholder="Something elegant" class="form-control" ></textarea>
</div>
</div>
</form>
</div>
<div class="panel-footer">
<button ng-click="save(account)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary pull-right">Save</button>
<div class="clearfix"></div>
</div>
</div>

View File

@ -1,53 +0,0 @@
'use strict';
angular.module('lemur')
.service('AccountApi', function (LemurRestangular) {
return LemurRestangular.all('accounts');
})
.service('AccountService', function ($location, AccountApi, toaster) {
var AccountService = this;
AccountService.findAccountsByName = function (filterValue) {
return AccountApi.getList({'filter[label]': filterValue})
.then(function (accounts) {
return accounts;
});
};
AccountService.create = function (account) {
AccountApi.post(account).then(
function () {
toaster.pop({
type: 'success',
title: account.label,
body: 'Successfully created!'
});
$location.path('accounts');
},
function (response) {
toaster.pop({
type: 'error',
title: account.label,
body: 'Was not created! ' + response.data.message
});
});
};
AccountService.update = function (account) {
account.put().then(
function () {
toaster.pop({
type: 'success',
title: account.label,
body: 'Successfully updated!'
});
$location.path('accounts');
},
function (response) {
toaster.pop({
type: 'error',
title: account.label,
body: 'Was not updated! ' + response.data.message
});
});
};
return AccountService;
});

View File

@ -1,52 +0,0 @@
'use strict';
angular.module('lemur')
.config(function config($routeProvider) {
$routeProvider.when('/accounts', {
templateUrl: '/angular/accounts/view/view.tpl.html',
controller: 'AccountsViewController'
});
})
.controller('AccountsViewController', function ($scope, AccountApi, AccountService, ngTableParams, toaster) {
$scope.filter = {};
$scope.accountsTable = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
id: 'desc' // initial sorting
},
filter: $scope.filter
}, {
total: 0, // length of data
getData: function ($defer, params) {
AccountApi.getList(params.url()).then(
function (data) {
params.total(data.total);
$defer.resolve(data);
}
);
}
});
$scope.remove = function (account) {
account.remove().then(
function () {
$scope.accountsTable.reload();
},
function (response) {
toaster.pop({
type: 'error',
title: 'Opps',
body: 'I see what you did there' + response.data.message
});
}
);
};
$scope.toggleFilter = function (params) {
params.settings().$scope.show_filter = !params.settings().$scope.show_filter;
};
});

View File

@ -1,44 +0,0 @@
<div class="row">
<div class="col-md-12">
<h2 class="featurette-heading">Accounts
<span class="text-muted"><small>next in line please</small></span></h2>
<div class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<a href="#/accounts/create" class="btn btn-primary">Create</a>
</div>
<div class="btn-group">
<button ng-click="toggleFilter(accountsTable)" class="btn btn-default">Filter</button>
</div>
<div class="clearfix"></div>
</div>
<div class="table-responsive">
<table ng-table="accountsTable" class="table table-striped" show-filter="false" template-pagination="angular/pager.html" >
<tbody>
<tr ng-repeat="account in $data track by $index">
<td data-title="'Name'" sortable="'label'" filter="{ 'label': 'text' }">
<ul class="list-unstyled">
<li>{{ account.label }}</li>
<li><span class="text-muted">{{ account.comments }}</span></li>
</ul>
</td>
<td data-title="'Account Number'" sortable="'account_number'" filter="{ 'account_number': 'text' }">
{{ account.accountNumber }}
</td>
<td data-title="''">
<div class="btn-group-vertical pull-right">
<a tooltip="Edit Account" href="#/accounts/{{ account.id }}/edit" class="btn btn-sm btn-info">
Edit
</a>
<button tooltip="Delete Account" ng-click="remove(account)" type="button" class="btn btn-sm btn-danger pull-left">
Remove
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@ -2,17 +2,6 @@
angular.module('lemur')
.config(function config($routeProvider) {
$routeProvider.when('/authorities/create', {
templateUrl: '/angular/authorities/authority/authorityWizard.tpl.html',
controller: 'AuthorityCreateController'
});
$routeProvider.when('/authorities/:id/edit', {
templateUrl: '/angular/authorities/authority/authorityEdit.tpl.html',
controller: 'AuthorityEditController'
});
})
.controller('AuthorityEditController', function ($scope, $routeParams, AuthorityApi, AuthorityService, RoleService){
AuthorityApi.get($routeParams.id).then(function (authority) {
AuthorityService.getRoles(authority);
@ -24,16 +13,21 @@ angular.module('lemur')
$scope.roleService = RoleService;
})
.controller('AuthorityCreateController', function ($scope, $modal, AuthorityService, LemurRestangular, RoleService) {
.controller('AuthorityCreateController', function ($scope, $modalInstance, AuthorityService, LemurRestangular, RoleService, PluginService, WizardHandler) {
$scope.authority = LemurRestangular.restangularizeElement(null, {}, 'authorities');
$scope.save = function (authority) {
var loadingModal = $modal.open({backdrop: 'static', template: '<wave-spinner></wave-spinner>', windowTemplateUrl: 'angular/loadingModal.html', size: 'large'});
return AuthorityService.create(authority).then(function (response) {
loadingModal.close();
});
$scope.loading = false;
$scope.create = function (authority) {
WizardHandler.wizard().context.loading = true;
AuthorityService.create(authority).then(function (resposne) {
WizardHandler.wizard().context.loading = false;
$modalInstance.close();
})
};
PluginService.get('issuer').then(function (plugins) {
$scope.plugins = plugins;
});
$scope.roleService = RoleService;

View File

@ -1,17 +1,21 @@
<h2 class="featurette-heading"><span ng-show="!authority.id">Create</span><span ng-show="authority.id">Edit</span> Authority <span class="text-muted"><small>The nail that sticks out farthest gets hammered the hardest
<div>
<wizard on-finish="save(authority)" template="angular/wizard.html">
<div class="modal-header">
<h3 class="modal-title"><span ng-show="!authority.id">Create</span><span ng-show="authority.id">Edit</span> Authority <span class="text-muted"><small>The nail that sticks out farthest gets hammered the hardest</small></span></h3>
</div>
<div class="modal-body">
<div>
<wizard on-finish="create(authority)" template="angular/wizard.html">
<wz-step title="Tracking" canexit="exitTracking">
<ng-include src="'angular/authorities/authority/tracking.tpl.html'"></ng-include>
</wz-step>
<wz-step title="Distinguished Name" canenter="exitTracking" canexit="exitDN">
<ng-include src="'angular/authorities/authority/distinguishedName.tpl.html'"></ng-include>
</wz-step>
<wz-step title="Options" canenter="exitDN" canexit="exitOptions">
<ng-include src="'angular/authorities/authority/options.tpl.html'"></ng-include>
</wz-step>
<wz-step title="Distinguished Name" canenter="exitTracking" canexit="exitDN">
<ng-include src="'angular/authorities/authority/distinguishedName.tpl.html'"></ng-include>
</wz-step>
<wz-step title="Extensions" canenter="exitOptions" canexit="exitExtensions">
<ng-include src="'angular/authorities/authority/extensions.tpl.html'"></ng-include>
</wz-step>
</wizard>
</div>
</div>

View File

@ -15,7 +15,7 @@
<input type="text" ng-model="authority.caParent" placeholder="Parent Authority Name"
typeahead="authority.name for authority in authorityService.findAuthorityByName($viewValue)" typeahead-loading="loadingAuthorities"
class="form-control input-md" typeahead-min-wait="50"
tooltip="When you specifiy a subordinate certificate authority you must specific the parent authority"
tooltip="When you specify a subordinate certificate authority you must specific the parent authority"
tooltip-trigger="focus" tooltip-placement="top">
</div>
</div>
@ -69,10 +69,10 @@
</div>
<div class="form-group">
<label class="control-label col-sm-2">
Plugin Name
Plugin
</label>
<div class="col-sm-10">
<select class="form-control" ng-model="authority.pluginName" ng-options="option for option in ['cloudca', 'verisign']" ng-init="authority.pluginName = 'cloudca'" required></select>
<select class="form-control" ng-model="authority.pluginName" ng-options="plugin.slug as plugin.title for plugin in plugins" ng-init="authority.pluginName = 'cloudca-issuer'" required></select>
</div>
</div>
</div>

View File

@ -31,34 +31,37 @@
</div>
</div>
<div class="form-group"
ng-class="{'has-error': trackingForm.commonName.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.commanName.$dirty}">
ng-class="{'has-error': trackingForm.commonName.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.commonName.$dirty}">
<label class="control-label col-sm-2">
Common Name
</label>
<div class="col-sm-10">
<input name="commonName" ng-model="authority.caDN.commonName" placeholder="Common Name" class="form-control" required/>
<p ng-show="trackingForm.commandName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must enter a common name</p>
<p ng-show="trackingForm.commonName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must enter a common name</p>
</div>
</div>
<div class="form-group">
<div class="form-group"
ng-class="{'has-error': trackingForm.validityEnd.$invalid || trackingForm.validityStart.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.validityEnd.$dirty&&trackingForm.validityStart.$dirty}">
<label class="control-label col-sm-2">
Validity Range
</label>
<div class="col-sm-4">
<div>
<div class="input-group">
<input tooltip="Starting Date" class="form-control" datepicker-popup="yyyy/MM/dd" is-open="opened1" ng-model="authority.validityStart" />
<input name="validityStart" tooltip="Starting Date" class="form-control" datepicker-popup="yyyy/MM/dd" is-open="opened1" ng-model="authority.validityStart" required/>
<p ng-show="trackingForm.validityStart.$invalid && !trackingForm.validityStart.$pristine" class="help-block">A start date is required!</p>
<span class="input-group-btn">
<button class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</div>
</div>
</div>
<span class="text-center col-sm-2"><label><span class="glyphicon glyphicon-resize-horizontal"></span></label></span>
<span style="padding-top: 15px" class="text-center col-sm-2"><label><span class="glyphicon glyphicon-resize-horizontal"></span></label></span>
<div class="col-sm-4">
<div>
<div class="input-group">
<input tooltip="Ending Date" class="form-control" datepicker-popup="yyyy/MM/dd" is-open="opened2" ng-model="authority.validityEnd" />
<input name="validityEnd" tooltip="Ending Date" class="form-control" datepicker-popup="yyyy/MM/dd" is-open="opened2" ng-model="authority.validityEnd" required/>
<p ng-show="trackingForm.validityEnd.$invalid && !trackingForm.validityEnd.$pristine" class="help-block">A end date is required!</p>
<span class="input-group-btn">
<button class="btn btn-default" ng-click="open2($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>

View File

@ -9,7 +9,7 @@ angular.module('lemur')
});
})
.controller('AuthoritiesViewController', function ($scope, $q, AuthorityApi, AuthorityService, ngTableParams) {
.controller('AuthoritiesViewController', function ($scope, $q, $modal, AuthorityApi, AuthorityService, ngTableParams) {
$scope.filter = {};
$scope.authoritiesTable = new ngTableParams({
page: 1, // show first page
@ -43,4 +43,36 @@ angular.module('lemur')
params.settings().$scope.show_filter = !params.settings().$scope.show_filter;
};
$scope.edit = function (authorityId) {
var modalInstance = $modal.open({
animation: true,
templateUrl: '/angular/authorities/authority/authorityWizard.tpl.html',
controller: 'AuthorityEditController',
size: 'lg',
resolve: {
editId: function () {
return authorityId;
}
}
});
modalInstance.result.then(function () {
$scope.authoritiesTable.reload();
});
};
$scope.create = function () {
var modalInstance = $modal.open({
animation: true,
controller: 'AuthorityCreateController',
templateUrl: '/angular/authorities/authority/authorityWizard.tpl.html',
size: 'lg'
});
modalInstance.result.then(function () {
$scope.authoritiesTable.reload();
});
};
});

View File

@ -5,7 +5,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<a href="#/authorities/create" class="btn btn-primary">Create</a>
<button class="btn btn-primary" ng-click="create()">Create</button>
</div>
<div class="btn-group">
<button ng-click="toggleFilter(authoritiesTable)" class="btn btn-default">Filter</button>
@ -36,9 +36,9 @@
</td>
<td data-title="''">
<div class="btn-group-vertical pull-right">
<a tooltip="Edit Authority" href="#/authorities/{{ authority.id }}/edit" class="btn btn-sm btn-info">
<button tooltip="Edit Authority" ng-click="edit(authority.id)" class="btn btn-sm btn-info">
Edit
</a>
</button>
</div>
</td>
</tr>

View File

@ -1,18 +1,6 @@
'use strict';
angular.module('lemur')
.config(function config($routeProvider) {
$routeProvider.when('/certificates/create', {
templateUrl: '/angular/certificates/certificate/certificateWizard.tpl.html',
controller: 'CertificateCreateController'
});
$routeProvider.when('/certificates/:id/edit', {
templateUrl: '/angular/certificates/certificate/edit.tpl.html',
controller: 'CertificateEditController'
});
})
.controller('CertificateEditController', function ($scope, $routeParams, CertificateApi, CertificateService, MomentService) {
CertificateApi.get($routeParams.id).then(function (certificate) {
$scope.certificate = certificate;
@ -23,13 +11,14 @@ angular.module('lemur')
})
.controller('CertificateCreateController', function ($scope, $modal, CertificateApi, CertificateService, AccountService, ELBService, AuthorityService, MomentService, LemurRestangular) {
.controller('CertificateCreateController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, ELBService, AuthorityService, PluginService, MomentService, WizardHandler, LemurRestangular) {
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
$scope.save = function (certificate) {
var loadingModal = $modal.open({backdrop: 'static', template: '<wave-spinner></wave-spinner>', windowTemplateUrl: 'angular/loadingModal.html', size: 'large'});
$scope.create = function (certificate) {
WizardHandler.wizard().context.loading = true;
CertificateService.create(certificate).then(function (response) {
loadingModal.close();
WizardHandler.wizard().context.loading = false;
$modalInstance.close();
});
};
@ -88,7 +77,11 @@ angular.module('lemur')
};
PluginService.get('destination').then(function (plugins) {
$scope.plugins = plugins;
});
$scope.elbService = ELBService;
$scope.authorityService = AuthorityService;
$scope.accountService = AccountService;
$scope.destinationService = DestinationService;
});

View File

@ -1,20 +0,0 @@
<h2 class="featurette-heading">Create a certificate <span class="text-muted"><small>encrypt all the things
</small></span></h2>
<div class="panel panel-default">
<div class="panel-heading">
<a href="/#/certificates/" class="btn btn-danger pull-right">Cancel</a>
<div class="clearfix"></div>
</div>
<div class="panel-body">
<form name="createForm" class="form-horizontal" role="form" ng-submit="submitCreate()" novalidate>
</form>
</div>
<div class="panel-footer">
<button type="submit" ng-disabled="createForm.$invalid" class="btn btn-success pull-right"><span
ng-show="!loading"> Create </span><span ng-show="loading">Creating <i ng-show="loading"
class="fa fa-cog fa-spin"></i></span>
</button>
<div class="clearfix"></div>
</div>
</div>

View File

@ -1,17 +1,23 @@
<h2 class="featurette-heading"><span ng-show="!certificate.id">Create</span><span ng-show="certificate.id">Edit</span> Certificate <span class="text-muted"><small>encrypt all the things
<div class="modal-header">
<h3 class="modal-title"><span ng-show="!certificate.id">Create</span><span ng-show="certificate.id">Edit</span> Certificate <span class="text-muted"><small>encrypt all the things</small></h3>
</div>
<div class="modal-body">
<div>
<wizard on-finish="save(certificate)" template="angular/wizard.html">
<wizard on-finish="create(certificate)" template="angular/wizard.html">
<wz-step title="Tracking" canexit="trackingForm.$valid">
<ng-include src="'angular/certificates/certificate/tracking.tpl.html'"></ng-include>
</wz-step>
<wz-step title="Distinguished Name" canenter="exitTracking" canexit="exitDN">
<ng-include src="'angular/certificates/certificate/distinguishedName.tpl.html'"></ng-include>
</wz-step>
<wz-step title="Options" canenter="enterValidation">
<ng-include src="'angular/certificates/certificate/options.tpl.html'"></ng-include>
</wz-step>
<wz-step title="Distinguished Name" canenter="exitTracking" canexit="exitDN">
<ng-include src="'angular/certificates/certificate/distinguishedName.tpl.html'"></ng-include>
</wz-step>
<wz-step title="Destinations" canenter="enterValidation">
<ng-include src="'angular/certificates/certificate/destinations.tpl.html'"></ng-include>
</wz-step>
</wizard>
</div>
</div>

View File

@ -1,62 +1,28 @@
<p>Destinations are purely optional, if you think the created certificate will be used in AWS select one or more accounts and Lemur will upload it for you.</p>
<div class="form-horizontal">
<div class="form-group">
<div class="form-group">
<label class="control-label col-sm-2">
Accounts
Destinations
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" ng-model="certificate.selectedAccount" placeholder="Account Name"
typeahead="account.label for account in accountService.findAccountsByName($viewValue)" typeahead-loading="loadingAccounts"
class="form-control input-md" typeahead-on-select="certificate.attachAccount($item)" typeahead-min-wait="50"
tooltip="Lemur can upload the certificate to any AWS account."
<input type="text" ng-model="certificate.selectedDestination" placeholder="AWS, TheSecret..."
typeahead="destination.label for destination in destinationService.findDestinationsByName($viewValue)" typeahead-loading="loadingDestinations"
class="form-control input-md" typeahead-on-select="certificate.attachDestination($item)" typeahead-min-wait="50"
tooltip="Lemur can upload certificates to any pre-defined destination"
tooltip-trigger="focus" tooltip-placement="top">
<span class="input-group-btn">
<button ng-model="accounts.show" class="btn btn-md btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
<span class="badge">{{ certificate.accounts.length || 0 }}</span>
<button ng-model="destinations.show" class="btn btn-md btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
<span class="badge">{{ certificate.destinations.length || 0 }}</span>
</button>
</span>
</div>
<table class="table">
<tr ng-repeat="account in certificate.accounts track by $index">
<td><a class="btn btn-sm btn-info" href="#/accounts/{{ account.id }}/certificates">{{ account.label }}</a></td>
<td><span class="text-muted">{{ account.comments }}</span></td>
<tr ng-repeat="destination in certificate.destinations track by $index">
<td><a class="btn btn-sm btn-info" href="#/destinations/{{ destination.id }}/certificates">{{ destination.label }}</a></td>
<td><span class="text-muted">{{ destination.description }}</span></td>
<td>
<button type="button" ng-click="certificate.removeAccount($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
<button type="button" ng-click="certificate.removeDestination($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
</td>
</tr>
</table>
</div>
</div>
<!--<div ng-show="certificate.accounts" class="form-group">
<label class="control-label col-sm-2">
ELBs
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" ng-model="certificate.selectedELB" placeholder="ELB Name"
typeahead="elb.name for elb in elbService.findELBByName($viewValue)" typeahead-loading="loadingAccounts"
class="form-control col-md-4" typeahead-min-wait="50"
tooltip="Lemur can upload a certificate to one or more ELBs"
tooltip-trigger="focus" tooltip-placement="top"/>
<input class="form-control col-md-2" type="integer" ng-model="certificate.selectedPort" placeholder="Port"/>
<select class="form-control col-md-2" ng-options="item for item in ['https', 'tcp']"></select>
<span class="input-group-btn">
<button ng-click="certificate.attachELB()" class="btn btn-info">Add</button>
</span>
</div>
<table class="table">
<tr ng-repeat="elb in certificate.elbs track by $index">
<td><a class="btn btn-sm btn-info" href="#/accounts/{{ elb.id }}/elbs">{{ elb.name }}</a></td>
<td>{{ elb.region }}</td>
<td>{{ elb.scheme }}</td>
<td>{{ elb.vpcId }}</td>
<td>{{ elb.listener.scheme }}</td>
<td>{{ elb.listener.port }}</td>
<td>
<button type="button" ng-click="certificate.removeELB($index)" class="btn btn-danger btn-sm pull-right">remove</button>
</td>
</tr>
</table>
</div>-->
</div>

View File

@ -47,7 +47,7 @@
Common Name
</label>
<div class="col-sm-10">
<input name="commonName" tooltip="If you need a certificate with multiple domains enter your primary domain here and the rest under 'Subject Alternate Names' in the options panel" ng-model="certificate.commonName" placeholder="Common Name" class="form-control" required/>
<input name="commonName" tooltip="If you need a certificate with multiple domains enter your primary domain here and the rest under 'Subject Alternate Names' in the next panel" ng-model="certificate.commonName" placeholder="Common Name" class="form-control" required/>
<p ng-show="trackingForm.commonName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must enter a common name</p>
</div>
</div>
@ -65,7 +65,7 @@
</div>
</div>
</div>
<span class="text-center col-sm-2"><label><span class="glyphicon glyphicon-resize-horizontal"></span></label></span>
<span style="padding-top: 15px" class="text-center col-sm-2"><label><span class="glyphicon glyphicon-resize-horizontal"></span></label></span>
<div class="col-sm-4">
<div>
<div class="input-group">

View File

@ -2,20 +2,16 @@
angular.module('lemur')
.config(function config($routeProvider) {
$routeProvider.when('/certificates/upload', {
templateUrl: '/angular/certificates/certificate/upload.tpl.html',
controller: 'CertificatesUploadController'
});
})
.controller('CertificatesUploadController', function ($scope, CertificateService, LemurRestangular, AccountService, ELBService) {
.controller('CertificateUploadController', function ($scope, $modalInstance, CertificateService, LemurRestangular, DestinationService, ELBService, PluginService) {
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
$scope.upload = CertificateService.upload;
$scope.accountService = AccountService;
$scope.destinationService = DestinationService;
$scope.elbService = ELBService;
PluginService.get('destination').then(function (plugins) {
$scope.plugins = plugins;
});
$scope.attachELB = function (elb) {
$scope.certificate.attachELB(elb);
@ -23,4 +19,9 @@ angular.module('lemur')
$scope.certificate.elb.listeners = listeners;
});
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});

View File

@ -1,11 +1,8 @@
<h2 class="featurette-heading">Upload a certificate <span class="text-muted"><small>encrypt all the things
</small></span></h2>
<div class="panel panel-default">
<div class="panel-heading">
<a href="/#/certificates/" class="btn btn-danger pull-right">Cancel</a>
<div class="clearfix"></div>
<div class="modal-header">
<div class="modal-title">
<h3 class="modal-header">Upload a certificate <span class="text-muted"><small>encrypt all the things</small></span></h3>
</div>
<div class="panel-body">
<div class="modal-body">
<form name="uploadForm" class="form-horizontal" role="form" novalidate>
<div class="form-group"
ng-class="{'has-error': uploadForm.owner.$invalid, 'has-success': !uploadForm.owner.$invalid&&uploadForm.owner.$dirty}">
@ -64,73 +61,11 @@
class="help-block">Enter a valid certificate.</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">
Accounts
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" ng-model="certificate.selectedAccount" placeholder="Account Name"
typeahead="account.label for account in accountService.findAccountsByName($viewValue)" typeahead-loading="loadingAccounts"
class="form-control" typeahead-on-select="certificate.attachAccount($item)" typeahead-min-wait="50"
tooltip="Lemur can upload the certificate to any AWS account."
tooltip-trigger="focus" tooltip-placement="top">
<span class="input-group-btn">
<button ng-model="certificate.accounts" class="btn btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
<span class="badge">{{ certificate.accounts.length || 0 }}</span>
</button>
</span>
</div>
<table ng-show="certificate.accounts" class="table">
<tr ng-repeat="account in certificate.accounts track by $index">
<td><a class="btn btn-sm btn-info" href="#/accounts/{{ account.id }}/certificates">{{ account.label }}</a></td>
<td><span class="text-muted">{{ account.comments }}</span></td>
<td>
<button type="button" ng-click="certificate.removeAccount($index)" class="btn btn-danger btn-sm pull-right">
Remove
</button>
</td>
</tr>
</table>
</div>
</div>
<!--<div class="form-group">
<label class="control-label col-sm-2">
ELBs
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" ng-model="certificate.selectedELB" placeholder="ELB Name"
typeahead="elb.name for elb in elbService.findELBByName($viewValue)" typeahead-loading="loadingAccounts"
class="form-control" typeahead-on-select="attachELB($item)" typeahead-min-wait="50"
tooltip="Lemur can upload a certificate to one or more ELBs searching will be limited to the accounts selected above"
tooltip-trigger="focus" tooltip-placement="top">
<span class="input-group-btn">
<button ng-model="certificate.elbs" class="btn btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
<span class="badge">{{ certificate.elbs.length || 0 }}</span>
</button>
</span>
</div>
<table ng-show="certificate.elbs" class="table">
<tr ng-repeat="elb in certificate.elbs track by $index">
<td><a class="btn btn-sm btn-info" href="#/accounts/{{ elb.id }}/elbs">{{ elb.name }}</a></td>
<td>{{ elb.region }}</td>
<td>{{ elb.scheme }}</td>
<td>{{ elb.vpcId }}</td>
<td><a class="btn btn-info" ng-model="elb.showListeners" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">View Listeners</a></td>
<td>
<button type="button" ng-click="certificate.removeAccount($index)" class="btn btn-danger btn-sm pull-right">
Remove
</button>
</td>
</tr>
</table>
</div>
</div>-->
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
</form>
</div>
<div class="panel-footer">
<button type="submit" ng-click="upload(certificate)" ng-disabled="uploadForm.$invalid" class="btn btn-success pull-right">Upload</button>
<div class="clearfix"></div>
<div class="modal-footer">
<button type="submit" ng-click="upload(certificate)" ng-disabled="uploadForm.$invalid" class="btn btn-success">Import</button>
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
</div>
</div>

View File

@ -58,15 +58,15 @@ angular.module('lemur')
removeCustom: function (index) {
this.extensions.custom.splice(index, 1);
},
attachAccount: function (account) {
this.selectedAccount = null;
if (this.accounts === undefined) {
this.accounts = [];
attachDestination: function (destination) {
this.selectedDestination = null;
if (this.destinations === undefined) {
this.destinations = [];
}
this.accounts.push(account);
this.destinations.push(destination);
},
removeAccount: function (index) {
this.accounts.splice(index, 1);
removeDestination: function (index) {
this.destinations.splice(index, 1);
},
attachELB: function (elb) {
this.selectedELB = null;
@ -99,13 +99,6 @@ angular.module('lemur')
});
};
CertificateService.getARNs = function (certificate) {
certificate.arns = [];
_.each(certificate.accounts, function (account) {
certificate.arns.push('arn:aws:iam::' + account.accountNumber + ':server-certificate/' + certificate.name);
});
};
CertificateService.create = function (certificate) {
certificate.attachSubAltName();
return CertificateApi.post(certificate).then(
@ -191,10 +184,9 @@ angular.module('lemur')
});
};
CertificateService.getAccounts = function (certificate) {
certificate.getList('accounts').then(function (accounts) {
certificate.accounts = accounts;
CertificateService.getARNs(certificate);
CertificateService.getDestinations = function (certificate) {
certificate.getList('destinations').then(function (destinations) {
certificate.destinations = destinations;
});
};

View File

@ -9,7 +9,7 @@ angular.module('lemur')
});
})
.controller('CertificatesViewController', function ($q, $scope, CertificateApi, CertificateService, MomentService, ngTableParams) {
.controller('CertificatesViewController', function ($q, $scope, $modal, CertificateApi, CertificateService, MomentService, ngTableParams) {
$scope.filter = {};
$scope.certificateTable = new ngTableParams({
page: 1, // show first page
@ -26,7 +26,7 @@ angular.module('lemur')
// TODO we should attempt to resolve all of these in parallel
_.each(data, function (certificate) {
CertificateService.getDomains(certificate);
CertificateService.getAccounts(certificate);
CertificateService.getDestinations(certificate);
CertificateService.getListeners(certificate);
CertificateService.getAuthority(certificate);
CertificateService.getCreator(certificate);
@ -60,4 +60,30 @@ angular.module('lemur')
$scope.toggleFilter = function (params) {
params.settings().$scope.show_filter = !params.settings().$scope.show_filter;
};
$scope.create = function () {
var modalInstance = $modal.open({
animation: true,
controller: 'CertificateCreateController',
templateUrl: '/angular/certificates/certificate/certificateWizard.tpl.html',
size: 'lg'
});
modalInstance.result.then(function () {
$scope.certificateTable.reload();
});
};
$scope.import = function () {
var modalInstance = $modal.open({
animation: true,
controller: 'CertificateUploadController',
templateUrl: '/angular/certificates/certificate/upload.tpl.html',
size: 'lg'
});
modalInstance.result.then(function () {
$scope.certificateTable.reload();
});
};
});

View File

@ -5,14 +5,14 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<a data-placement="left" data-title="Create Certificate" bs-tooltip href="#/certificates/create"
<button data-placement="left" data-title="Create Certificate" bs-tooltip ng-click="create()"
class="btn btn-primary">
Create
</a>
<a data-placement="left" data-title="Upload Certificate" bs-tooltip href="#/certificates/upload"
</button>
<button data-placement="left" data-title="Import Certificate" bs-tooltip ng-click="import()"
class="btn btn-info">
Upload
</a>
Import
</button>
</div>
<div class="btn-group">
<button ng-click="toggleFilter(certificateTable)" class="btn btn-default">Filter</button>
@ -30,9 +30,9 @@
<li><span class="text-muted">{{ certificate.owner }}</span></li>
</ul>
</td>
<td data-title="'Accounts'" filter="{ 'account': 'select' }" filter-date="getAccountDropDown()">
<td data-title="'Destinations'" filter="{ 'destination': 'select' }" filter-date="getDestinationDropDown()">
<div class="btn-group">
<a href="#/accounts/{{ account.id }}/edit" class="btn btn-sm btn-default" ng-repeat="account in certificate.accounts">{{ account.label }}</a>
<a href="#/destinations/{{ destination.id }}/edit" class="btn btn-sm btn-default" ng-repeat="account in certificate.destinations">{{ destination.label }}</a>
</div>
</td>
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getCertificateStatus()">
@ -106,7 +106,7 @@
<div class="list-group">
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="domain in certificate.domains">{{ domain.name }}</a>
</div>
<h4 ng-show="certificate.accounts.total">ARNs</h4>
<h4 ng-show="certificate.destinations.total">ARNs</h4>
<ul class="list-group">
<li class="list-group-item" ng-repeat="arn in certificate.arns">{{ arn }}</li>
</ul>

View File

@ -1,8 +1,8 @@
angular.module('lemur').
filter('titleCase', function () {
return function (str) {
return (str === undefined || str === null) ? '' : str.replace(/_|-/, ' ').replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
return (str === undefined || str === null) ? '' : str.replace(/([A-Z])/g, ' $1').replace(/^./, function (txt) {
return txt.toUpperCase();
});
};
});

View File

@ -0,0 +1,40 @@
'use strict';
angular.module('lemur')
.controller('DestinationsCreateController', function ($scope, $modalInstance, PluginService, DestinationService, LemurRestangular){
$scope.destination = LemurRestangular.restangularizeElement(null, {}, 'destinations');
PluginService.get('destination').then(function (plugins) {
$scope.plugins = plugins;
});
$scope.save = function (destination) {
DestinationService.create(destination).then(function () {
$modalInstance.close();
});
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
})
.controller('DestinationsEditController', function ($scope, $modalInstance, DestinationService, DestinationApi, PluginService, editId) {
DestinationApi.get(editId).then(function (destination) {
$scope.destination = destination;
});
PluginService.get('destination').then(function (plugins) {
$scope.plugins = plugins;
});
$scope.save = function (destination) {
DestinationService.update(destination).then(function () {
$modalInstance.close();
});
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});

View File

@ -0,0 +1,56 @@
<div class="modal-header">
<div class="modal-title">
<h3 class="modal-header"><span ng-show="!destination.fromServer">Create</span><span ng-show="destination.fromServer">Edit</span> Destination <span class="text-muted"><small>oh the places you will go!</small></span></h3>
</div>
<div class="modal-body">
<form name="createForm" class="form-horizontal" role="form" novalidate>
<div class="form-group"
ng-class="{'has-error': createForm.label.$invalid, 'has-success': !createForm.label.$invalid&&createForm.label.$dirty}">
<label class="control-label col-sm-2">
Label
</label>
<div class="col-sm-10">
<input name="label" ng-model="destination.label" placeholder="Label" class="form-control" required/>
<p ng-show="createForm.label.$invalid && !createForm.label.$pristine" class="help-block">You must enter an destination label</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">
Description
</label>
<div class="col-sm-10">
<textarea name="comments" ng-model="destination.description" placeholder="Something elegant" class="form-control" ></textarea>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">
Plugin
</label>
<div class="col-sm-10">
<select class="form-control" ng-model="destination.plugin" ng-options="plugin.title for plugin in plugins" required></select>
</div>
</div>
<div class="form-group" ng-repeat="item in destination.plugin.pluginOptions">
<ng-form name="subForm" class="form-horizontal" role="form" novalidate>
<div ng-class="{'has-error': subForm.sub.$invalid, 'has-success': !subForm.sub.$invalid&&subForm.sub.$dirty}">
<label class="control-label col-sm-2">
{{ item.name | titleCase }}
</label>
<div class="col-sm-10">
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="/^[0-9]{12,12}$/" class="form-control" ng-model="item.value"/>
<select name="sub" ng-if="item.type == 'select'" class="form-control" ng-options="i for i in item.available" ng-model="item.value"></select>
<input name="sub" ng-if="item.type == 'bool'" class="form-control" type="checkbox" ng-model="item.value">
<input name="sub" ng-if="item.type == 'str'" type="text" class="form-control" ng-model="item.value"/>
<p ng-show="subForm.sub.$invalid && !subForm.sub.$pristine" class="help-block">{{ item.helpMessage }}</p>
</div>
</div>
</ng-form>
</div>
</form>
</div>
<div class="modal-footer">
<button ng-click="save(destination)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save</button>
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
</div>
</div>

View File

@ -0,0 +1,53 @@
'use strict';
angular.module('lemur')
.service('DestinationApi', function (LemurRestangular) {
return LemurRestangular.all('destinations');
})
.service('DestinationService', function ($location, DestinationApi, toaster) {
var DestinationService = this;
DestinationService.findDestinationsByName = function (filterValue) {
return DestinationApi.getList({'filter[label]': filterValue})
.then(function (destinations) {
return destinations;
});
};
DestinationService.create = function (destination) {
return DestinationApi.post(destination).then(
function () {
toaster.pop({
type: 'success',
title: destination.label,
body: 'Successfully created!'
});
$location.path('destinations');
},
function (response) {
toaster.pop({
type: 'error',
title: destination.label,
body: 'Was not created! ' + response.data.message
});
});
};
DestinationService.update = function (destination) {
return destination.put().then(
function () {
toaster.pop({
type: 'success',
title: destination.label,
body: 'Successfully updated!'
});
$location.path('destinations');
},
function (response) {
toaster.pop({
type: 'error',
title: destination.label,
body: 'Was not updated! ' + response.data.message
});
});
};
return DestinationService;
});

View File

@ -0,0 +1,85 @@
'use strict';
angular.module('lemur')
.config(function config($routeProvider) {
$routeProvider.when('/destinations', {
templateUrl: '/angular/destinations/view/view.tpl.html',
controller: 'DestinationsViewController'
});
})
.controller('DestinationsViewController', function ($scope, $modal, DestinationApi, DestinationService, ngTableParams, toaster) {
$scope.filter = {};
$scope.destinationsTable = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
id: 'desc' // initial sorting
},
filter: $scope.filter
}, {
total: 0, // length of data
getData: function ($defer, params) {
DestinationApi.getList(params.url()).then(
function (data) {
params.total(data.total);
$defer.resolve(data);
}
);
}
});
$scope.remove = function (destination) {
destination.remove().then(
function () {
$scope.destinationsTable.reload();
},
function (response) {
toaster.pop({
type: 'error',
title: 'Opps',
body: 'I see what you did there' + response.data.message
});
}
);
};
$scope.edit = function (destinationId) {
var modalInstance = $modal.open({
animation: true,
templateUrl: '/angular/destinations/destination/destination.tpl.html',
controller: 'DestinationsEditController',
size: 'lg',
resolve: {
editId: function () {
return destinationId;
}
}
});
modalInstance.result.then(function () {
$scope.destinationsTable.reload();
});
};
$scope.create = function () {
var modalInstance = $modal.open({
animation: true,
controller: 'DestinationsCreateController',
templateUrl: '/angular/destinations/destination/destination.tpl.html',
size: 'lg'
});
modalInstance.result.then(function () {
$scope.destinationsTable.reload();
});
};
$scope.toggleFilter = function (params) {
params.settings().$scope.show_filter = !params.settings().$scope.show_filter;
};
});

View File

@ -0,0 +1,47 @@
<div class="row">
<div class="col-md-12">
<h2 class="featurette-heading">Destinations
<span class="text-muted"><small>oh the places you will go</small></span></h2>
<div class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<button ng-click="create()" class="btn btn-primary">Create</button>
</div>
<div class="btn-group">
<button ng-click="toggleFilter(destinationsTable)" class="btn btn-default">Filter</button>
</div>
<div class="clearfix"></div>
</div>
<div class="table-responsive">
<table ng-table="destinationsTable" class="table table-striped" show-filter="false" template-pagination="angular/pager.html" >
<tbody>
<tr ng-repeat="destination in $data track by $index">
<td data-title="'Label'" sortable="'label'" filter="{ 'label': 'text' }">
<ul class="list-unstyled">
<li>{{ destination.label }}</li>
<li><span class="text-muted">{{ destination.description }}</span></li>
</ul>
</td>
<td data-title="'Plugin'">
<ul class="list-unstyled">
<li>{{ destination.plugin.title }}</li>
<li><span class="text-muted">{{ destination.plugin.description }}</span></li>
</ul>
</td>
<td data-title="''">
<div class="btn-group-vertical pull-right">
<button tooltip="Edit Destination" ng-click="edit(destination.id)" class="btn btn-sm btn-info">
Edit
</button>
<button tooltip="Delete Destination" ng-click="remove(destination)" type="button" class="btn btn-sm btn-danger pull-left">
Remove
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@ -1,6 +0,0 @@
<div tabindex="-1" role="dialog" class="modal fade" ng-class="{in: animate}" ng-style="{'z-index': 1050 + index*10, display: 'block'}">
<div class="modal-dialog-center">
<div modal-transclude>
</div>
</div>
</div>

View File

@ -2,29 +2,37 @@
angular.module('lemur')
.config(function config($routeProvider) {
$routeProvider.when('/roles/create', {
templateUrl: '/angular/roles/role/role.tpl.html',
controller: 'RoleCreateController'
});
$routeProvider.when('/roles/:id/edit', {
templateUrl: '/angular/roles/role/role.tpl.html',
controller: 'RoleEditController'
});
})
.controller('RoleEditController', function ($scope, $routeParams, RoleApi, RoleService, UserService) {
RoleApi.get($routeParams.id).then(function (role) {
.controller('RolesEditController', function ($scope, $modalInstance, RoleApi, RoleService, UserService, editId) {
RoleApi.get(editId).then(function (role) {
$scope.role = role;
RoleService.getUsers(role);
});
$scope.save = RoleService.update;
$scope.save = function (role) {
RoleService.update(role).then(function () {
$modalInstance.close();
});
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.userService = UserService;
$scope.roleService = RoleService;
})
.controller('RoleCreateController', function ($scope, RoleApi, RoleService, UserService, LemurRestangular ) {
.controller('RolesCreateController', function ($scope,$modalInstance, RoleApi, RoleService, UserService, LemurRestangular) {
$scope.role = LemurRestangular.restangularizeElement(null, {}, 'roles');
$scope.userService = UserService;
$scope.save = RoleService.create;
$scope.save = function (role) {
RoleService.create(role).then(function () {
$modalInstance.close();
});
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});

View File

@ -1,12 +1,8 @@
<h2 class="featurette-heading"><span ng-show="!role.fromServer">Create</span><span ng-show="role.fromServer">Edit</span> Role <span class="text-muted"><small>The nail that sticks out farthest gets hammered the hardest
</small></span></h2>
<div class="panel panel-default">
<div class="panel-heading">
<button ng-click="roleService.loadPassword(role)" class="btn btn-warning">Show Credentials</button>
<a href="/#/roles" class="btn btn-danger pull-right">Cancel</a>
<div class="clearfix"></div>
<div class="modal-header">
<div class="modal-title">
<h3 class="modal-header"><span ng-show="!role.fromServer">Create</span><span ng-show="role.fromServer">Edit</span> Role <span class="text-muted"><small>The nail that sticks out farthest gets hammered the hardest</small></span></h3>
</div>
<div class="panel-body">
<div class="modal-body">
<form name="createForm" class="form-horizontal" ng-submit="save(role)" role="form" novalidate>
<div class="form-group"
ng-class="{'has-error': createForm.name.$invalid, 'has-success': !createForm.name.$invalid&&createForm.name.$dirty}">
@ -77,9 +73,10 @@
</div>
</div>
</form>
<div class="modal-footer">
<button ng-click="roleService.loadPassword(role)" class="btn btn-warning pull-left">Show Credentials</button>
<button ng-click="save(role)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save</button>
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
</div>
<div class="panel-footer">
<button ng-click="save(role)" class="btn btn-success pull-right">Save</button>
<div class="clearfix"></div>
</div>
</div>

View File

@ -38,7 +38,7 @@ angular.module('lemur')
};
RoleService.create = function (role) {
RoleApi.post(role).then(
return RoleApi.post(role).then(
function () {
toaster.pop({
type: 'success',
@ -57,7 +57,7 @@ angular.module('lemur')
};
RoleService.update = function (role) {
role.put().then(
return role.put().then(
function () {
toaster.pop({
type: 'success',

View File

@ -9,7 +9,7 @@ angular.module('lemur')
});
})
.controller('RolesViewController', function ($scope, RoleApi, RoleService, ngTableParams) {
.controller('RolesViewController', function ($scope, $modal, RoleApi, RoleService, ngTableParams) {
$scope.filter = {};
$scope.rolesTable = new ngTableParams({
page: 1, // show first page
@ -39,4 +39,38 @@ angular.module('lemur')
params.settings().$scope.show_filter = !params.settings().$scope.show_filter;
};
$scope.edit = function (roleId) {
var modalInstance = $modal.open({
animation: true,
templateUrl: '/angular/roles/role/role.tpl.html',
controller: 'RolesEditController',
size: 'lg',
resolve: {
editId: function () {
return roleId;
}
}
});
modalInstance.result.then(function () {
$scope.rolesTable.reload();
});
};
$scope.create = function () {
var modalInstance = $modal.open({
animation: true,
controller: 'RolesCreateController',
templateUrl: '/angular/roles/role/role.tpl.html',
size: 'lg'
});
modalInstance.result.then(function () {
$scope.rolesTable.reload();
});
};
});

View File

@ -5,7 +5,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<a data-placement="left" data-title="Create Role" bs-tooltip href="#/roles/create" class="btn btn-primary">Create</a>
<button ng-click="create()" class="btn btn-primary">Create</button>
</div>
<div class="btn-group">
<button ng-click="toggleFilter(rolesTable)" class="btn btn-default">Filter</button>
@ -24,9 +24,9 @@
</td>
<td data-title="''">
<div class="btn-group-vertical pull-right">
<a href="#/roles/{{ role.id }}/edit" class="btn btn-sm btn-info">
<button ng-click="edit(role.id)" class="btn btn-sm btn-info">
Edit
</a>
</button>
<a ng-click="remove(role)" class="btn btn-sm btn-danger">
Remove
</a>

View File

@ -50,7 +50,7 @@ angular.module('lemur')
};
UserService.create = function (user) {
UserApi.post(user).then(
return UserApi.post(user).then(
function () {
toaster.pop({
type: 'success',
@ -69,7 +69,7 @@ angular.module('lemur')
};
UserService.update = function (user) {
user.put().then(
return user.put().then(
function () {
toaster.pop({
type: 'success',

View File

@ -2,19 +2,8 @@
angular.module('lemur')
.config(function config($routeProvider) {
$routeProvider.when('/users/create', {
templateUrl: '/angular/users/user/user.tpl.html',
controller: 'UsersCreateController'
});
$routeProvider.when('/users/:id/edit', {
templateUrl: '/angular/users/user/user.tpl.html',
controller: 'UsersEditController'
});
})
.controller('UsersEditController', function ($scope, $routeParams, UserApi, UserService, RoleService) {
UserApi.get($routeParams.id).then(function (user) {
.controller('UsersEditController', function ($scope, $modalInstance, UserApi, UserService, RoleService, editId) {
UserApi.get(editId).then(function (user) {
UserService.getRoles(user);
$scope.user = user;
});
@ -24,15 +13,36 @@ angular.module('lemur')
$scope.rolePage = 1;
$scope.save = function (user) {
UserService.update(user).then(function () {
$modalInstance.close();
});
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.loadMoreRoles = function () {
$scope.rolePage += 1;
UserService.loadMoreRoles($scope.user, $scope.rolePage);
};
})
.controller('UsersCreateController', function ($scope, UserService, LemurRestangular, RoleService) {
.controller('UsersCreateController', function ($scope, $modalInstance, UserService, LemurRestangular, RoleService) {
$scope.user = LemurRestangular.restangularizeElement(null, {}, 'users');
$scope.save = UserService.create;
$scope.roleService = RoleService;
$scope.create = function (user) {
UserService.create(user).then(function () {
$modalInstance.close();
});
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});

View File

@ -1,11 +1,8 @@
<h2 class="featurette-heading"><span ng-show="!user.fromServer">Create</span><span ng-show="user.fromServer">Edit</span> User <span class="text-muted"><small>what was your name again?
</small></span></h2>
<div class="panel panel-default">
<div class="panel-heading">
<a href="#/users" class="btn btn-danger pull-right">Cancel</a>
<div class="clearfix"></div>
<div class="modal-header">
<div class="modal-title">
<h3 class="modal-header"><span ng-show="!user.fromServer">Create</span><span ng-show="user.fromServer">Edit</span> User <span class="text-muted"><small>what was your name again?</small></span></h3>
</div>
<div class="panel-body">
<div class="modal-body">
<form name="createForm" class="form-horizontal" role="form" novalidate>
<div class="form-group"
ng-class="{'has-error': createForm.username.$invalid, 'has-success': !createForm.username.$invalid&&createForm.username.$dirty}">
@ -43,7 +40,7 @@
</label>
<div class="col-sm-10">
<div class="checkbox col-sm-10">
<input ng-model="user.active" type="checkbox" value="true">
<switch ng-model="user.active" id="active" name="active" ng-init="user.active=true" class="green small"></switch>
</div>
</div>
</div>
@ -81,9 +78,9 @@
</div>
</div>
</form>
<div class="modal-footer">
<button ng-click="save(user)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save</button>
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
</div>
<div class="panel-footer">
<button ng-click="save(user)" class="btn btn-success pull-right">Save</button>
<div class="clearfix"></div>
</div>
</div>

View File

@ -9,7 +9,7 @@ angular.module('lemur')
});
})
.controller('UsersViewController', function ($scope, UserApi, UserService, ngTableParams) {
.controller('UsersViewController', function ($scope, $modal, UserApi, UserService, ngTableParams) {
$scope.filter = {};
$scope.usersTable = new ngTableParams({
page: 1, // show first page
@ -36,6 +36,39 @@ angular.module('lemur')
});
};
$scope.edit = function (userId) {
var modalInstance = $modal.open({
animation: true,
templateUrl: '/angular/users/user/user.tpl.html',
controller: 'UsersEditController',
size: 'lg',
resolve: {
editId: function () {
return userId;
}
}
});
modalInstance.result.then(function () {
$scope.usersTable.reload();
});
};
$scope.create = function () {
var modalInstance = $modal.open({
animation: true,
controller: 'UsersCreateController',
templateUrl: '/angular/users/user/user.tpl.html',
size: 'lg'
});
modalInstance.result.then(function () {
$scope.usersTable.reload();
});
};
$scope.toggleFilter = function (params) {
params.settings().$scope.show_filter = !params.settings().$scope.show_filter;
};

View File

@ -5,7 +5,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<a href="#/users/create" class="btn btn-primary">Create</a>
<button ng-click="create()" class="btn btn-primary">Create</button>
</div>
<div class="btn-group">
<button ng-click="toggleFilter(usersTable)" class="btn btn-default">Filter</button>
@ -29,9 +29,9 @@
</td>
<td data-title="''">
<div class="btn-group-vertical pull-right">
<a tooltip="Edit User" href="#/users/{{ user.id }}/edit" class="btn btn-sm btn-info">
<button tooltip="Edit User" ng-click="edit(user.id)" class="btn btn-sm btn-info">
Edit
</a>
</button>
</div>
</td>
</tr>

View File

@ -1,23 +1,12 @@
<div>
<div class="panel panel-default">
<div class="panel-heading">
<ul class="steps-indicator steps-{{steps.length}}" ng-if="!hideIndicators">
<li ng-class="{default: !step.completed && !step.selected, current: step.selected && !step.completed, done: step.completed && !step.selected, editing: step.selected && step.completed}" ng-repeat="step in steps">
<a ng-click="goTo(step)">{{step.title || step.wzTitle}}</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="panel-body">
<div class="modal-body">
<div class="steps" ng-transclude></div>
</div>
<div class="panel-footer">
<div class="modal-footer">
<input ng-hide="currentStepNumber() == 1" class="btn btn-default pull-left" type="submit" wz-previous value="Previous" />
<input ng-show="currentStepNumber() != steps.length" class="btn btn-default pull-right" type="submit" wz-next value="Next" />
<button ng-show="currentStepNumber() == steps.length" class="btn btn-success pull-right" type="submit" wz-next>
Create
</button>
<input ng-show="!context.loading" class="btn btn-success pull-right" type="submit" wz-finish value="Create" />
<button ng-show="context.loading" class="btn btn-success pull-right disabled"><wave-spinner></wave-spinner></button>
<div class="clearfix"></div>
</div>
</div>
</div>

View File

@ -55,7 +55,7 @@
<li data-match-route="/domains"><a href="/#/domains">Domains</a></li>
<li><a href="/#/roles">Roles</a></li>
<li><a href="/#/users">Users</a></li>
<li><a href="/#/accounts">Accounts</a></li>
<li><a href="/#/destinations">Destinations</a></li>
</ul>
<ul ng-show="!currentUser.username" class="nav navbar-nav navbar-right">
<li><a href="/#/login">Login</a></li>

View File

@ -159,3 +159,13 @@ a {
margin-top: 10px;
}
.wave-spinner {
margin: 5px auto !important;
width: 40px !important;
height: 12px !important;
}
.wave-spinner>div {
background-color: #FFFFFF !important;
}

View File

@ -1,15 +1,15 @@
from lemur.accounts.service import *
from lemur.accounts.views import *
from lemur.destinations.service import *
from lemur.destinations.views import *
from json import dumps
def test_crud(session):
account = create('111111', 'account1')
assert account.id > 0
destination = create('111111', 'destination1')
assert destination.id > 0
account = update(account.id, 11111, 'account2')
assert account.label == 'account2'
destination = update(destination.id, 11111, 'destination2')
assert destination.label == 'destination2'
assert len(get_all()) == 1
@ -17,116 +17,116 @@ def test_crud(session):
assert len(get_all()) == 0
def test_account_get(client):
assert client.get(api.url_for(Accounts, account_id=1)).status_code == 401
def test_destination_get(client):
assert client.get(api.url_for(Destinations, destination_id=1)).status_code == 401
def test_account_post(client):
assert client.post(api.url_for(Accounts, account_id=1), data={}).status_code == 405
def test_destination_post(client):
assert client.post(api.url_for(Destinations, destination_id=1), data={}).status_code == 405
def test_account_put(client):
assert client.put(api.url_for(Accounts, account_id=1), data={}).status_code == 401
def test_destination_put(client):
assert client.put(api.url_for(Destinations, destination_id=1), data={}).status_code == 401
def test_account_delete(client):
assert client.delete(api.url_for(Accounts, account_id=1)).status_code == 401
def test_destination_delete(client):
assert client.delete(api.url_for(Destinations, destination_id=1)).status_code == 401
def test_account_patch(client):
assert client.patch(api.url_for(Accounts, account_id=1), data={}).status_code == 405
def test_destination_patch(client):
assert client.patch(api.url_for(Destinations, destination_id=1), data={}).status_code == 405
VALID_USER_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
def test_auth_account_get(client):
assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
def test_auth_destination_get(client):
assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
def test_auth_account_post_(client):
assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_destination_post_(client):
assert client.post(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_account_put(client):
assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
def test_auth_destination_put(client):
assert client.put(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
def test_auth_account_delete(client):
assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403
def test_auth_destination_delete(client):
assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403
def test_auth_account_patch(client):
assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_destination_patch(client):
assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
VALID_ADMIN_HEADER_TOKEN = {
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
def test_admin_account_get(client):
assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
def test_admin_destination_get(client):
assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
def test_admin_account_post(client):
assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_destination_post(client):
assert client.post(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_account_put(client):
assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
def test_admin_destination_put(client):
assert client.put(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
def test_admin_account_delete(client):
assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500
def test_admin_destination_delete(client):
assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500
def test_admin_account_patch(client):
assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_destination_patch(client):
assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_accounts_get(client):
assert client.get(api.url_for(AccountsList)).status_code == 401
def test_destinations_get(client):
assert client.get(api.url_for(DestinationsList)).status_code == 401
def test_accounts_post(client):
assert client.post(api.url_for(AccountsList), data={}).status_code == 401
def test_destinations_post(client):
assert client.post(api.url_for(DestinationsList), data={}).status_code == 401
def test_accounts_put(client):
assert client.put(api.url_for(AccountsList), data={}).status_code == 405
def test_destinations_put(client):
assert client.put(api.url_for(DestinationsList), data={}).status_code == 405
def test_accounts_delete(client):
assert client.delete(api.url_for(AccountsList)).status_code == 405
def test_destinations_delete(client):
assert client.delete(api.url_for(DestinationsList)).status_code == 405
def test_accounts_patch(client):
assert client.patch(api.url_for(AccountsList), data={}).status_code == 405
def test_destinations_patch(client):
assert client.patch(api.url_for(DestinationsList), data={}).status_code == 405
def test_auth_accounts_get(client):
assert client.get(api.url_for(AccountsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200
def test_auth_destinations_get(client):
assert client.get(api.url_for(DestinationsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200
def test_auth_accounts_post(client):
assert client.post(api.url_for(AccountsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
def test_auth_destinations_post(client):
assert client.post(api.url_for(DestinationsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
def test_admin_accounts_get(client):
resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN)
def test_admin_destinations_get(client):
resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN)
assert resp.status_code == 200
assert resp.json == {'items': [], 'total': 0}
def test_admin_accounts_crud(client):
assert client.post(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
data = {'accountNumber': 111, 'label': 'test', 'comments': 'test'}
resp = client.post(api.url_for(AccountsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN)
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'}
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(Accounts, account_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN)
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': [{'accountNumber': 111, 'label': 'test', 'comments': 'test', 'id': 2}], 'total': 1}
assert client.delete(api.url_for(Accounts, account_id=2), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN)
assert resp.json == {'items': [{'destinationNumber': 111, 'label': 'test', 'comments': 'test', 'id': 2}], 'total': 1}
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
assert resp.json == {'items': [], 'total': 0}

View File

@ -106,8 +106,11 @@ setup(
'lemur = lemur.manage:main',
],
'lemur.plugins': [
'verisign = lemur.plugins.lemur_verisign.plugin:VerisignPlugin',
'cloudca = lemur.plugins.lemur_cloudca.plugin:CloudCAPlugin',
'verisign_issuer = lemur.plugins.lemur_verisign.plugin:VerisignIssuerPlugin',
'cloudca_issuer = lemur.plugins.lemur_cloudca.plugin:CloudCAIssuerPlugin',
'cloudca_source = lemur.plugins.lemur_cloudca.plugin:CloudCASourcePlugin'
'aws_destination = lemur.plugins.lemur_aws.plugin:AWSDestinationPlugin',
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin'
],
},
classifiers=[