We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
A custom cert name with spaces causes AWS Upload failures
Creating a cert with a custom name that has spaces, such as: `My Certificate` will not properly get uploaded to AWS.

-- Potential Fixes:
1. Prevent spaces in custom names
2. Allow custom cert names to be editable
3. If spaces are allowed, the AWS uploader plugin needs to upload it in a way that can work properly.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lemur/certificates/models.py`
Content:
```
1 """
2 .. module: lemur.certificates.models
3     :platform: Unix
4     :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
5     :license: Apache, see LICENSE for more details.
6 .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
7 """
8 import datetime
9 
10 import lemur.common.utils
11 from flask import current_app
12 
13 from sqlalchemy.orm import relationship
14 from sqlalchemy.sql.expression import case
15 from sqlalchemy.ext.hybrid import hybrid_property
16 from sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean
17 
18 from lemur.database import db
19 from lemur.models import certificate_associations, certificate_source_associations, \
20     certificate_destination_associations, certificate_notification_associations, \
21     certificate_replacement_associations, roles_certificates
22 from lemur.plugins.base import plugins
23 from lemur.utils import Vault
24 
25 from lemur.common import defaults
26 from lemur.domains.models import Domain
27 
28 
29 def get_or_increase_name(name):
30     count = Certificate.query.filter(Certificate.name.ilike('{0}%'.format(name))).count()
31 
32     if count >= 1:
33         return name + '-' + str(count)
34 
35     return name
36 
37 
38 class Certificate(db.Model):
39     __tablename__ = 'certificates'
40     id = Column(Integer, primary_key=True)
41     owner = Column(String(128), nullable=False)
42     name = Column(String(128), unique=True)
43     description = Column(String(1024))
44     notify = Column(Boolean, default=True)
45 
46     body = Column(Text(), nullable=False)
47     chain = Column(Text())
48     private_key = Column(Vault)
49 
50     issuer = Column(String(128))
51     serial = Column(String(128))
52     cn = Column(String(128))
53     deleted = Column(Boolean, index=True)
54 
55     not_before = Column(DateTime)
56     not_after = Column(DateTime)
57     date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
58 
59     signing_algorithm = Column(String(128))
60     status = Column(String(128))
61     bits = Column(Integer())
62     san = Column(String(1024))  # TODO this should be migrated to boolean
63 
64     user_id = Column(Integer, ForeignKey('users.id'))
65     authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE"))
66     root_authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE"))
67 
68     notifications = relationship("Notification", secondary=certificate_notification_associations, backref='certificate')
69     destinations = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
70     sources = relationship("Source", secondary=certificate_source_associations, backref='certificate')
71     domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
72     roles = relationship("Role", secondary=roles_certificates, backref="certificate")
73     replaces = relationship("Certificate",
74                             secondary=certificate_replacement_associations,
75                             primaryjoin=id == certificate_replacement_associations.c.certificate_id,  # noqa
76                             secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id,  # noqa
77                             backref='replaced')
78 
79     endpoints = relationship("Endpoint", backref='certificate')
80 
81     def __init__(self, **kwargs):
82         cert = lemur.common.utils.parse_certificate(kwargs['body'])
83 
84         self.issuer = defaults.issuer(cert)
85         self.cn = defaults.common_name(cert)
86         self.san = defaults.san(cert)
87         self.not_before = defaults.not_before(cert)
88         self.not_after = defaults.not_after(cert)
89 
90         # when destinations are appended they require a valid name.
91         if kwargs.get('name'):
92             self.name = get_or_increase_name(kwargs['name'])
93         else:
94             self.name = get_or_increase_name(defaults.certificate_name(self.cn, self.issuer, self.not_before, self.not_after, self.san))
95 
96         self.owner = kwargs['owner']
97         self.body = kwargs['body'].strip()
98 
99         if kwargs.get('private_key'):
100             self.private_key = kwargs['private_key'].strip()
101 
102         if kwargs.get('chain'):
103             self.chain = kwargs['chain'].strip()
104 
105         self.destinations = kwargs.get('destinations', [])
106         self.notifications = kwargs.get('notifications', [])
107         self.description = kwargs.get('description')
108         self.roles = list(set(kwargs.get('roles', [])))
109         self.replaces = kwargs.get('replacements', [])
110         self.signing_algorithm = defaults.signing_algorithm(cert)
111         self.bits = defaults.bitstrength(cert)
112         self.serial = defaults.serial(cert)
113 
114         for domain in defaults.domains(cert):
115             self.domains.append(Domain(name=domain))
116 
117     @property
118     def active(self):
119         if self.endpoints:
120             return True
121 
122     @hybrid_property
123     def expired(self):
124         if self.not_after <= datetime.datetime.now():
125             return True
126 
127     @expired.expression
128     def expired(cls):
129         return case(
130             [
131                 (cls.now_after <= datetime.datetime.now(), True)
132             ],
133             else_=False
134         )
135 
136     @hybrid_property
137     def revoked(self):
138         if 'revoked' == self.status:
139             return True
140 
141     @revoked.expression
142     def revoked(cls):
143         return case(
144             [
145                 (cls.status == 'revoked', True)
146             ],
147             else_=False
148         )
149 
150     def get_arn(self, account_number):
151         """
152         Generate a valid AWS IAM arn
153 
154         :rtype : str
155         :param account_number:
156         :return:
157         """
158         return "arn:aws:iam::{}:server-certificate/{}".format(account_number, self.name)
159 
160 
161 @event.listens_for(Certificate.destinations, 'append')
162 def update_destinations(target, value, initiator):
163     """
164     Attempt to upload the new certificate to the new destination
165 
166     :param target:
167     :param value:
168     :param initiator:
169     :return:
170     """
171     destination_plugin = plugins.get(value.plugin_name)
172 
173     try:
174         destination_plugin.upload(target.name, target.body, target.private_key, target.chain, value.options)
175     except Exception as e:
176         current_app.logger.exception(e)
177 
178 
179 @event.listens_for(Certificate.replaces, 'append')
180 def update_replacement(target, value, initiator):
181     """
182     When a certificate is marked as 'replaced' it is then marked as in-active
183 
184     :param target:
185     :param value:
186     :param initiator:
187     :return:
188     """
189     value.active = False
190 
191 
192 @event.listens_for(Certificate, 'before_update')
193 def protect_active(mapper, connection, target):
194     """
195     When a certificate has a replacement do not allow it to be marked as 'active'
196 
197     :param connection:
198     :param mapper:
199     :param target:
200     :return:
201     """
202     if target.active:
203         if not target.notify:
204             raise Exception(
205                 "Cannot silence notification for a certificate Lemur has been found to be currently deployed onto endpoints"
206             )
207
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py
--- a/lemur/certificates/models.py
+++ b/lemur/certificates/models.py
@@ -27,6 +27,7 @@
 
 
 def get_or_increase_name(name):
+    name = '-'.join(name.strip().split(' '))
     count = Certificate.query.filter(Certificate.name.ilike('{0}%'.format(name))).count()
 
     if count >= 1:

{"golden_diff": "diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py\n--- a/lemur/certificates/models.py\n+++ b/lemur/certificates/models.py\n@@ -27,6 +27,7 @@\n \n \n def get_or_increase_name(name):\n+    name = '-'.join(name.strip().split(' '))\n     count = Certificate.query.filter(Certificate.name.ilike('{0}%'.format(name))).count()\n \n     if count >= 1:\n", "issue": "A custom cert name with spaces causes AWS Upload failures\nCreating a cert with a custom name that has spaces, such as: `My Certificate` will not properly get uploaded to AWS.\n\n-- Potential Fixes:\n1. Prevent spaces in custom names\n2. Allow custom cert names to be editable\n3. If spaces are allowed, the AWS uploader plugin needs to upload it in a way that can work properly.\n\n", "before_files": [{"content": "\"\"\"\n.. module: lemur.certificates.models\n    :platform: Unix\n    :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more\n    :license: Apache, see LICENSE for more details.\n.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>\n\"\"\"\nimport datetime\n\nimport lemur.common.utils\nfrom flask import current_app\n\nfrom sqlalchemy.orm import relationship\nfrom sqlalchemy.sql.expression import case\nfrom sqlalchemy.ext.hybrid import hybrid_property\nfrom sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean\n\nfrom lemur.database import db\nfrom lemur.models import certificate_associations, certificate_source_associations, \\\n    certificate_destination_associations, certificate_notification_associations, \\\n    certificate_replacement_associations, roles_certificates\nfrom lemur.plugins.base import plugins\nfrom lemur.utils import Vault\n\nfrom lemur.common import defaults\nfrom lemur.domains.models import Domain\n\n\ndef get_or_increase_name(name):\n    count = Certificate.query.filter(Certificate.name.ilike('{0}%'.format(name))).count()\n\n    if count >= 1:\n        return name + '-' + str(count)\n\n    return name\n\n\nclass Certificate(db.Model):\n    __tablename__ = 'certificates'\n    id = Column(Integer, primary_key=True)\n    owner = Column(String(128), nullable=False)\n    name = Column(String(128), unique=True)\n    description = Column(String(1024))\n    notify = Column(Boolean, default=True)\n\n    body = Column(Text(), nullable=False)\n    chain = Column(Text())\n    private_key = Column(Vault)\n\n    issuer = Column(String(128))\n    serial = Column(String(128))\n    cn = Column(String(128))\n    deleted = Column(Boolean, index=True)\n\n    not_before = Column(DateTime)\n    not_after = Column(DateTime)\n    date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)\n\n    signing_algorithm = Column(String(128))\n    status = Column(String(128))\n    bits = Column(Integer())\n    san = Column(String(1024))  # TODO this should be migrated to boolean\n\n    user_id = Column(Integer, ForeignKey('users.id'))\n    authority_id = Column(Integer, ForeignKey('authorities.id', ondelete=\"CASCADE\"))\n    root_authority_id = Column(Integer, ForeignKey('authorities.id', ondelete=\"CASCADE\"))\n\n    notifications = relationship(\"Notification\", secondary=certificate_notification_associations, backref='certificate')\n    destinations = relationship(\"Destination\", secondary=certificate_destination_associations, backref='certificate')\n    sources = relationship(\"Source\", secondary=certificate_source_associations, backref='certificate')\n    domains = relationship(\"Domain\", secondary=certificate_associations, backref=\"certificate\")\n    roles = relationship(\"Role\", secondary=roles_certificates, backref=\"certificate\")\n    replaces = relationship(\"Certificate\",\n                            secondary=certificate_replacement_associations,\n                            primaryjoin=id == certificate_replacement_associations.c.certificate_id,  # noqa\n                            secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id,  # noqa\n                            backref='replaced')\n\n    endpoints = relationship(\"Endpoint\", backref='certificate')\n\n    def __init__(self, **kwargs):\n        cert = lemur.common.utils.parse_certificate(kwargs['body'])\n\n        self.issuer = defaults.issuer(cert)\n        self.cn = defaults.common_name(cert)\n        self.san = defaults.san(cert)\n        self.not_before = defaults.not_before(cert)\n        self.not_after = defaults.not_after(cert)\n\n        # when destinations are appended they require a valid name.\n        if kwargs.get('name'):\n            self.name = get_or_increase_name(kwargs['name'])\n        else:\n            self.name = get_or_increase_name(defaults.certificate_name(self.cn, self.issuer, self.not_before, self.not_after, self.san))\n\n        self.owner = kwargs['owner']\n        self.body = kwargs['body'].strip()\n\n        if kwargs.get('private_key'):\n            self.private_key = kwargs['private_key'].strip()\n\n        if kwargs.get('chain'):\n            self.chain = kwargs['chain'].strip()\n\n        self.destinations = kwargs.get('destinations', [])\n        self.notifications = kwargs.get('notifications', [])\n        self.description = kwargs.get('description')\n        self.roles = list(set(kwargs.get('roles', [])))\n        self.replaces = kwargs.get('replacements', [])\n        self.signing_algorithm = defaults.signing_algorithm(cert)\n        self.bits = defaults.bitstrength(cert)\n        self.serial = defaults.serial(cert)\n\n        for domain in defaults.domains(cert):\n            self.domains.append(Domain(name=domain))\n\n    @property\n    def active(self):\n        if self.endpoints:\n            return True\n\n    @hybrid_property\n    def expired(self):\n        if self.not_after <= datetime.datetime.now():\n            return True\n\n    @expired.expression\n    def expired(cls):\n        return case(\n            [\n                (cls.now_after <= datetime.datetime.now(), True)\n            ],\n            else_=False\n        )\n\n    @hybrid_property\n    def revoked(self):\n        if 'revoked' == self.status:\n            return True\n\n    @revoked.expression\n    def revoked(cls):\n        return case(\n            [\n                (cls.status == 'revoked', True)\n            ],\n            else_=False\n        )\n\n    def get_arn(self, account_number):\n        \"\"\"\n        Generate a valid AWS IAM arn\n\n        :rtype : str\n        :param account_number:\n        :return:\n        \"\"\"\n        return \"arn:aws:iam::{}:server-certificate/{}\".format(account_number, self.name)\n\n\n@event.listens_for(Certificate.destinations, 'append')\ndef update_destinations(target, value, initiator):\n    \"\"\"\n    Attempt to upload the new certificate to the new destination\n\n    :param target:\n    :param value:\n    :param initiator:\n    :return:\n    \"\"\"\n    destination_plugin = plugins.get(value.plugin_name)\n\n    try:\n        destination_plugin.upload(target.name, target.body, target.private_key, target.chain, value.options)\n    except Exception as e:\n        current_app.logger.exception(e)\n\n\n@event.listens_for(Certificate.replaces, 'append')\ndef update_replacement(target, value, initiator):\n    \"\"\"\n    When a certificate is marked as 'replaced' it is then marked as in-active\n\n    :param target:\n    :param value:\n    :param initiator:\n    :return:\n    \"\"\"\n    value.active = False\n\n\n@event.listens_for(Certificate, 'before_update')\ndef protect_active(mapper, connection, target):\n    \"\"\"\n    When a certificate has a replacement do not allow it to be marked as 'active'\n\n    :param connection:\n    :param mapper:\n    :param target:\n    :return:\n    \"\"\"\n    if target.active:\n        if not target.notify:\n            raise Exception(\n                \"Cannot silence notification for a certificate Lemur has been found to be currently deployed onto endpoints\"\n            )\n", "path": "lemur/certificates/models.py"}], "after_files": [{"content": "\"\"\"\n.. module: lemur.certificates.models\n    :platform: Unix\n    :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more\n    :license: Apache, see LICENSE for more details.\n.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>\n\"\"\"\nimport datetime\n\nimport lemur.common.utils\nfrom flask import current_app\n\nfrom sqlalchemy.orm import relationship\nfrom sqlalchemy.sql.expression import case\nfrom sqlalchemy.ext.hybrid import hybrid_property\nfrom sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean\n\nfrom lemur.database import db\nfrom lemur.models import certificate_associations, certificate_source_associations, \\\n    certificate_destination_associations, certificate_notification_associations, \\\n    certificate_replacement_associations, roles_certificates\nfrom lemur.plugins.base import plugins\nfrom lemur.utils import Vault\n\nfrom lemur.common import defaults\nfrom lemur.domains.models import Domain\n\n\ndef get_or_increase_name(name):\n    name = '-'.join(name.strip().split(' '))\n    count = Certificate.query.filter(Certificate.name.ilike('{0}%'.format(name))).count()\n\n    if count >= 1:\n        return name + '-' + str(count)\n\n    return name\n\n\nclass Certificate(db.Model):\n    __tablename__ = 'certificates'\n    id = Column(Integer, primary_key=True)\n    owner = Column(String(128), nullable=False)\n    name = Column(String(128), unique=True)\n    description = Column(String(1024))\n    active = Column(Boolean, default=True)\n\n    body = Column(Text(), nullable=False)\n    chain = Column(Text())\n    private_key = Column(Vault)\n\n    issuer = Column(String(128))\n    serial = Column(String(128))\n    cn = Column(String(128))\n    deleted = Column(Boolean, index=True)\n\n    not_before = Column(DateTime)\n    not_after = Column(DateTime)\n    date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)\n\n    signing_algorithm = Column(String(128))\n    status = Column(String(128))\n    bits = Column(Integer())\n    san = Column(String(1024))  # TODO this should be migrated to boolean\n\n    user_id = Column(Integer, ForeignKey('users.id'))\n    authority_id = Column(Integer, ForeignKey('authorities.id', ondelete=\"CASCADE\"))\n    root_authority_id = Column(Integer, ForeignKey('authorities.id', ondelete=\"CASCADE\"))\n\n    notifications = relationship(\"Notification\", secondary=certificate_notification_associations, backref='certificate')\n    destinations = relationship(\"Destination\", secondary=certificate_destination_associations, backref='certificate')\n    sources = relationship(\"Source\", secondary=certificate_source_associations, backref='certificate')\n    domains = relationship(\"Domain\", secondary=certificate_associations, backref=\"certificate\")\n    roles = relationship(\"Role\", secondary=roles_certificates, backref=\"certificate\")\n    replaces = relationship(\"Certificate\",\n                            secondary=certificate_replacement_associations,\n                            primaryjoin=id == certificate_replacement_associations.c.certificate_id,  # noqa\n                            secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id,  # noqa\n                            backref='replaced')\n\n    endpoints = relationship(\"Endpoint\", backref='certificate')\n\n    def __init__(self, **kwargs):\n        cert = lemur.common.utils.parse_certificate(kwargs['body'])\n\n        self.issuer = defaults.issuer(cert)\n        self.cn = defaults.common_name(cert)\n        self.san = defaults.san(cert)\n        self.not_before = defaults.not_before(cert)\n        self.not_after = defaults.not_after(cert)\n\n        # when destinations are appended they require a valid name.\n        if kwargs.get('name'):\n            self.name = get_or_increase_name(kwargs['name'])\n        else:\n            self.name = get_or_increase_name(defaults.certificate_name(self.cn, self.issuer, self.not_before, self.not_after, self.san))\n\n        self.owner = kwargs['owner']\n        self.body = kwargs['body'].strip()\n\n        if kwargs.get('private_key'):\n            self.private_key = kwargs['private_key'].strip()\n\n        if kwargs.get('chain'):\n            self.chain = kwargs['chain'].strip()\n\n        self.destinations = kwargs.get('destinations', [])\n        self.notifications = kwargs.get('notifications', [])\n        self.description = kwargs.get('description')\n        self.roles = list(set(kwargs.get('roles', [])))\n        self.replaces = kwargs.get('replacements', [])\n        self.signing_algorithm = defaults.signing_algorithm(cert)\n        self.bits = defaults.bitstrength(cert)\n        self.serial = defaults.serial(cert)\n\n        for domain in defaults.domains(cert):\n            self.domains.append(Domain(name=domain))\n\n    @hybrid_property\n    def expired(self):\n        if self.not_after <= datetime.datetime.now():\n            return True\n\n    @expired.expression\n    def expired(cls):\n        return case(\n            [\n                (cls.now_after <= datetime.datetime.now(), True)\n            ],\n            else_=False\n        )\n\n    @hybrid_property\n    def revoked(self):\n        if 'revoked' == self.status:\n            return True\n\n    @revoked.expression\n    def revoked(cls):\n        return case(\n            [\n                (cls.status == 'revoked', True)\n            ],\n            else_=False\n        )\n\n    def get_arn(self, account_number):\n        \"\"\"\n        Generate a valid AWS IAM arn\n\n        :rtype : str\n        :param account_number:\n        :return:\n        \"\"\"\n        return \"arn:aws:iam::{}:server-certificate/{}\".format(account_number, self.name)\n\n\n@event.listens_for(Certificate.destinations, 'append')\ndef update_destinations(target, value, initiator):\n    \"\"\"\n    Attempt to upload the new certificate to the new destination\n\n    :param target:\n    :param value:\n    :param initiator:\n    :return:\n    \"\"\"\n    destination_plugin = plugins.get(value.plugin_name)\n\n    try:\n        destination_plugin.upload(target.name, target.body, target.private_key, target.chain, value.options)\n    except Exception as e:\n        current_app.logger.exception(e)\n\n\n@event.listens_for(Certificate.replaces, 'append')\ndef update_replacement(target, value, initiator):\n    \"\"\"\n    When a certificate is marked as 'replaced' it is then marked as in-active\n\n    :param target:\n    :param value:\n    :param initiator:\n    :return:\n    \"\"\"\n    value.active = False\n\n\n@event.listens_for(Certificate, 'before_update')\ndef protect_active(mapper, connection, target):\n    \"\"\"\n    When a certificate has a replacement do not allow it to be marked as 'active'\n\n    :param connection:\n    :param mapper:\n    :param target:\n    :return:\n    \"\"\"\n    if target.active:\n        if target.replaced:\n            raise Exception(\"Cannot mark certificate as active, certificate has been marked as replaced.\")\n", "path": "lemur/certificates/models.py"}]}