-
Notifications
You must be signed in to change notification settings - Fork 43
/
forms.py
191 lines (159 loc) · 5.45 KB
/
forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Define forms for generating access tokens and clients."""
from flask import current_app
from flask_wtf import FlaskForm as Form
from invenio_i18n import lazy_gettext as _
from oauthlib.oauth2.rfc6749.errors import (
InsecureTransportError,
InvalidRedirectURIError,
)
from werkzeug.local import LocalProxy
from wtforms import fields, validators, widgets
from wtforms.widgets import HTMLString
from wtforms_alchemy import model_form_factory
from .models import Client
from .theme.semantic.form_styling import SelectSUI
from .validators import URLValidator, validate_redirect_uri
#
# Widget
#
def scopes_multi_checkbox(field, **kwargs):
"""Render multi checkbox widget."""
kwargs.setdefault("type", "checkbox")
field_id = kwargs.pop("id", field.id)
html = ['<div class="row">']
for value, label, checked in field.iter_choices():
choice_id = "%s-%s" % (field_id, value)
options = dict(
kwargs,
name=field.name,
value=value,
id=choice_id,
class_=" ",
)
if checked:
options["checked"] = "checked"
html.append('<div class="col-md-3">')
html.append('<label for="{0}" class="checkbox-inline">'.format(choice_id))
html.append("<input {0} /> ".format(widgets.html_params(**options)))
html.append(
'{0} <br/><small class="text-muted">{1}</small>'.format(
value, label.help_text
)
)
html.append("</label></div>")
html.append("</div>")
return HTMLString("".join(html))
#
# Redirect URI field
#
class RedirectURIField(fields.TextAreaField):
"""Process redirect URI field data."""
def process_formdata(self, valuelist):
"""Process form data."""
if valuelist:
self.data = "\n".join(
[
x.strip()
for x in filter(lambda x: x, "\n".join(valuelist).splitlines())
]
)
def process_data(self, value):
"""Process data."""
self.data = "\n".join(value)
class RedirectURIValidator(object):
"""Validate if redirect URIs."""
def __call__(self, form, field):
"""Call function."""
errors = []
for v in field.data.splitlines():
try:
validate_redirect_uri(v)
except InsecureTransportError:
errors.append(v)
except InvalidRedirectURIError:
errors.append(v)
if errors:
raise validators.ValidationError(
_("Invalid redirect URIs: {urls}").format(urls=", ".join(errors))
)
#
# Forms
#
class ClientFormBase(model_form_factory(Form)):
"""Base class for Client form."""
class Meta:
"""Metadata class."""
model = Client
exclude = [
"client_secret",
"is_internal",
]
strip_string_fields = True
field_args = dict(
website=dict(
validators=[validators.DataRequired(), URLValidator()],
widget=widgets.TextInput(),
),
)
class ClientForm(ClientFormBase):
"""Client form."""
# Trick to make redirect_uris render in the bottom of the form.
redirect_uris = RedirectURIField(
label=_("Redirect URIs (one per line)"),
description=_(
"One redirect URI per line. This is your application's"
" authorization callback URLs. HTTPS must be used for all "
"hosts except localhost (for testing purposes)."
),
validators=[RedirectURIValidator(), validators.DataRequired()],
default="",
)
widget = LocalProxy(
lambda: (
SelectSUI()
if current_app.config.get("APP_THEME")
and current_app.config.get("APP_THEME")[0] == "semantic-ui"
else widgets.Select()
)
)
is_confidential = fields.SelectField(
label=_("Client type"),
description=_(
"Select confidential if your application is capable of keeping "
"the issued client secret confidential (e.g. a web application), "
"select public if your application cannot (e.g. a browser-based "
"JavaScript application). If you select public, your application "
"MUST validate the redirect URI."
),
coerce=lambda x: False if x == "False" else bool(x),
choices=[("True", _("Confidential")), ("False", _("Public"))],
default="True",
widget=widget,
)
class TokenForm(Form):
"""Token form."""
name = fields.StringField(
description=_("Name of personal access token."),
validators=[
validators.DataRequired(),
validators.Length(
max=40, message=_("The name must be less than 40 characters long.")
),
],
)
scopes = fields.SelectMultipleField(
widget=scopes_multi_checkbox,
choices=[], # Must be dynamically provided in view.
description=_(
"Scopes assign permissions to your personal access token."
" A personal access token works just like a normal OAuth "
" access token for authentication against the API."
),
)