Authorities marshmallow addition (#303)

This commit is contained in:
kevgliss
2016-05-09 11:00:16 -07:00
parent 776e0fcd11
commit df0ad4d875
19 changed files with 619 additions and 529 deletions

View File

@ -0,0 +1,79 @@
"""
.. module: lemur.authorities.schemas
: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 flask import current_app
from marshmallow import fields, validates_schema
from marshmallow import validate
from marshmallow.exceptions import ValidationError
from lemur.schemas import PluginSchema, ExtensionSchema, AssociatedAuthoritySchema, AssociatedRoleSchema
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
from lemur.common import validators
class AuthorityInputSchema(LemurInputSchema):
name = fields.String(required=True)
owner = fields.Email(required=True)
description = fields.String()
common_name = fields.String(required=True, validate=validators.sensitive_domain)
validity_start = fields.DateTime()
validity_end = fields.DateTime()
validity_years = fields.Integer()
# certificate body fields
organizational_unit = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'))
organization = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'))
location = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_LOCATION'))
country = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_COUNTRY'))
state = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_STATE'))
plugin = fields.Nested(PluginSchema)
# signing related options
type = fields.String(validate=validate.OneOf(['root', 'subca']), missing='root')
authority = fields.Nested(AssociatedAuthoritySchema)
signing_algorithm = fields.String(validate=validate.OneOf(['sha256WithRSA', 'sha1WithRSA']), missing='sha256WithRSA')
key_type = fields.String(validate=validate.OneOf(['RSA2048', 'RSA4096']), missing='RSA2048')
key_name = fields.String()
sensitivity = fields.String(validate=validate.OneOf(['medium', 'high']), missing='medium')
serial_number = fields.Integer()
first_serial = fields.Integer(missing=1)
extensions = fields.Nested(ExtensionSchema)
roles = fields.Nested(AssociatedRoleSchema(many=True))
@validates_schema
def validate_dates(self, data):
validators.dates(data)
@validates_schema
def validate_subca(self, data):
if data['type'] == 'subca':
if not data.get('authority'):
raise ValidationError("If generating a subca parent 'authority' must be specified.")
class AuthorityOutputSchema(LemurOutputSchema):
id = fields.Integer()
name = fields.String()
owner = fields.Email()
not_before = fields.DateTime()
not_after = fields.DateTime()
plugin_name = fields.String()
body = fields.String()
chain = fields.String()
active = fields.Boolean()
options = fields.Dict()
roles = fields.List(fields.Nested(AssociatedRoleSchema))
authority_input_schema = AuthorityInputSchema()
authority_output_schema = AuthorityOutputSchema()
authorities_output_schema = AuthorityOutputSchema(many=True)

View File

@ -19,8 +19,6 @@ from lemur.notifications import service as notification_service
from lemur.roles.models import Role
from lemur.certificates.models import Certificate
from lemur.plugins.base import plugins
def update(authority_id, description=None, owner=None, active=None, roles=None):
"""
@ -49,20 +47,20 @@ def create(kwargs):
:return:
"""
issuer = plugins.get(kwargs.get('pluginName'))
issuer = kwargs['plugin']
kwargs['creator'] = g.current_user.email
cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs)
cert = Certificate(cert_body, chain=intermediate)
cert.owner = kwargs['ownerEmail']
cert.owner = kwargs['owner']
if kwargs['caType'] == 'subca':
if kwargs['type'] == 'subca':
cert.description = "This is the ROOT certificate for the {0} sub certificate authority the parent \
authority is {1}.".format(kwargs.get('caName'), kwargs.get('caParent'))
authority is {1}.".format(kwargs.get('name'), kwargs.get('parent'))
else:
cert.description = "This is the ROOT certificate for the {0} certificate authority.".format(
kwargs.get('caName')
kwargs.get('name')
)
cert.user = g.current_user
@ -79,7 +77,7 @@ def create(kwargs):
role = role_service.create(
r['name'],
password=r['password'],
description="{0} auto generated role".format(kwargs.get('pluginName')),
description="{0} auto generated role".format(kwargs['plugin'].title),
username=r['username'])
# the user creating the authority should be able to administer it
@ -89,11 +87,11 @@ def create(kwargs):
role_objs.append(role)
authority = Authority(
kwargs.get('caName'),
kwargs['ownerEmail'],
kwargs['pluginName'],
kwargs.get('name'),
kwargs['owner'],
kwargs['plugin'].slug,
cert_body,
description=kwargs['caDescription'],
description=kwargs['description'],
chain=intermediate,
roles=role_objs
)
@ -102,10 +100,10 @@ def create(kwargs):
authority = database.create(authority)
# the owning dl or role should have this authority associated with it
owner_role = role_service.get_by_name(kwargs['ownerEmail'])
owner_role = role_service.get_by_name(kwargs['owner'])
if not owner_role:
owner_role = role_service.create(kwargs['ownerEmail'])
owner_role = role_service.create(kwargs['owner'])
owner_role.authority = authority
@ -170,10 +168,6 @@ def render(args):
:return:
"""
query = database.session_query(Authority)
sort_by = args.pop('sort_by')
sort_dir = args.pop('sort_dir')
page = args.pop('page')
count = args.pop('count')
filt = args.pop('filter')
if filt:
@ -191,9 +185,4 @@ def render(args):
authority_ids.append(role.authority.id)
query = query.filter(Authority.id.in_(authority_ids))
query = database.find_all(query, Authority, args)
if sort_by and sort_dir:
query = database.sort(query, Authority, sort_by, sort_dir)
return database.paginate(query, page, count)
return database.sort_and_page(query, Authority, args)

View File

@ -6,31 +6,19 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import Blueprint, g
from flask.ext.restful import reqparse, fields, Api
from flask.ext.restful import reqparse, Api
from lemur.authorities import service
from lemur.roles import service as role_service
from lemur.certificates import service as certificate_service
from lemur.common.utils import paginated_parser
from lemur.common.schema import validate_schema
from lemur.auth.service import AuthenticatedResource
from lemur.auth.permissions import AuthorityPermission
from lemur.common.utils import paginated_parser, marshal_items
from lemur.roles import service as role_service
from lemur.certificates import service as certificate_service
from lemur.authorities import service
from lemur.authorities.schemas import authority_input_schema, authority_output_schema, authorities_output_schema
FIELDS = {
'name': fields.String,
'owner': fields.String,
'description': fields.String,
'options': fields.Raw,
'pluginName': fields.String,
'body': fields.String,
'chain': fields.String,
'active': fields.Boolean,
'notBefore': fields.DateTime(dt_format='iso8601', attribute='not_before'),
'notAfter': fields.DateTime(dt_format='iso8601', attribute='not_after'),
'id': fields.Integer,
}
mod = Blueprint('authorities', __name__)
api = Api(mod)
@ -42,7 +30,7 @@ class AuthoritiesList(AuthenticatedResource):
self.reqparse = reqparse.RequestParser()
super(AuthoritiesList, self).__init__()
@marshal_items(FIELDS)
@validate_schema(None, authorities_output_schema)
def get(self):
"""
.. http:get:: /authorities
@ -98,8 +86,8 @@ class AuthoritiesList(AuthenticatedResource):
args = parser.parse_args()
return service.render(args)
@marshal_items(FIELDS)
def post(self):
@validate_schema(authority_input_schema, authority_output_schema)
def post(self, data=None):
"""
.. http:post:: /authorities
@ -180,25 +168,7 @@ class AuthoritiesList(AuthenticatedResource):
:statuscode 403: unauthenticated
:statuscode 200: no error
"""
self.reqparse.add_argument('caName', type=str, location='json', required=True)
self.reqparse.add_argument('caDescription', type=str, location='json', required=False)
self.reqparse.add_argument('ownerEmail', type=str, location='json', required=True)
self.reqparse.add_argument('caDN', type=dict, location='json', required=False)
self.reqparse.add_argument('validityStart', type=str, location='json', required=False) # TODO validate
self.reqparse.add_argument('validityEnd', type=str, location='json', required=False) # TODO validate
self.reqparse.add_argument('extensions', type=dict, location='json', required=False)
self.reqparse.add_argument('pluginName', type=str, location='json', required=True)
self.reqparse.add_argument('caType', type=str, location='json', required=False)
self.reqparse.add_argument('caParent', type=str, location='json', required=False)
self.reqparse.add_argument('caSigningAlgo', type=str, location='json', required=False)
self.reqparse.add_argument('keyType', type=str, location='json', required=False)
self.reqparse.add_argument('caSensitivity', type=str, location='json', required=False)
self.reqparse.add_argument('caKeyName', type=str, location='json', required=False)
self.reqparse.add_argument('caSerialNumber', type=int, location='json', required=False)
self.reqparse.add_argument('caFirstSerial', type=int, location='json', required=False)
args = self.reqparse.parse_args()
return service.create(args)
return service.create(data)
class Authorities(AuthenticatedResource):
@ -206,7 +176,7 @@ class Authorities(AuthenticatedResource):
self.reqparse = reqparse.RequestParser()
super(Authorities, self).__init__()
@marshal_items(FIELDS)
@validate_schema(None, authority_output_schema)
def get(self, authority_id):
"""
.. http:get:: /authorities/1
@ -248,8 +218,8 @@ class Authorities(AuthenticatedResource):
"""
return service.get(authority_id)
@marshal_items(FIELDS)
def put(self, authority_id):
@validate_schema(authority_input_schema, authority_output_schema)
def put(self, authority_id, data=None):
"""
.. http:put:: /authorities/1
@ -295,12 +265,6 @@ class Authorities(AuthenticatedResource):
:statuscode 200: no error
:statuscode 403: unauthenticated
"""
self.reqparse.add_argument('roles', type=list, default=[], location='json')
self.reqparse.add_argument('active', type=str, location='json', required=True)
self.reqparse.add_argument('owner', type=str, location='json', required=True)
self.reqparse.add_argument('description', type=str, location='json', required=True)
args = self.reqparse.parse_args()
authority = service.get(authority_id)
role = role_service.get_by_name(authority.owner)
@ -313,7 +277,7 @@ class Authorities(AuthenticatedResource):
# we want to make sure that we cannot add roles that we are not members of
if not g.current_user.is_admin:
role_ids = set([r['id'] for r in args['roles']])
role_ids = set([r['id'] for r in data['roles']])
user_role_ids = set([r.id for r in g.current_user.roles])
if not role_ids.issubset(user_role_ids):
@ -322,10 +286,10 @@ class Authorities(AuthenticatedResource):
if permission.can():
return service.update(
authority_id,
owner=args['owner'],
description=args['description'],
active=args['active'],
roles=args['roles']
owner=data['owner'],
description=data['description'],
active=data['active'],
roles=data['roles']
)
return dict(message="You are not authorized to update this authority"), 403
@ -333,10 +297,9 @@ class Authorities(AuthenticatedResource):
class CertificateAuthority(AuthenticatedResource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(CertificateAuthority, self).__init__()
@marshal_items(FIELDS)
@validate_schema(None, authority_output_schema)
def get(self, certificate_id):
"""
.. http:get:: /certificates/1/authority