43
43
import cgi
44
44
import cgitb
45
45
import codecs
46
+ from collections import defaultdict , namedtuple
46
47
from flask import Flask , request , Response
47
48
from functools import partial , update_wrapper
48
49
import os
56
57
from wsgiref .simple_server import WSGIRequestHandler
57
58
58
59
from mig .shared .accountstate import check_account_accessible
59
- from mig .shared .base import client_dir_id , client_id_dir , cert_field_map
60
+ from mig .shared .base import canonical_user , client_dir_id , client_id_dir , cert_field_map
60
61
from mig .shared .conf import get_configuration_object
61
62
from mig .shared .compat import PY2
62
63
from mig .shared .griddaemons .openid import default_max_user_hits , \
72
73
valid_complex_url , InputException
73
74
from mig .shared .tlsserver import hardened_ssl_context
74
75
from mig .shared .url import urlparse , urlencode , parse_qsl
75
- from mig .shared .useradm import create_user , get_any_oid_user_dn , check_password_scramble , \
76
+ from mig .shared .useradm import get_any_oid_user_dn , check_password_scramble , \
76
77
check_hash
77
78
from mig .shared .userdb import default_db_path
78
79
from mig .shared .validstring import possible_user_id , is_valid_email_address
80
+ from mig .server .createuser import _main as createuser
79
81
80
82
# Update with extra fields
81
83
cert_field_map .update ({'role' : 'ROLE' , 'timezone' : 'TZ' , 'nickname' : 'NICK' ,
@@ -114,8 +116,8 @@ def _ensure_encoded_string(chunk):
114
116
exc .code : exc for exc in httpexceptions .__dict__ .values () if hasattr (exc , 'code' )}
115
117
116
118
117
- def http_error_from_status_code (http_status_code , http_url ):
118
- return httpexceptions_by_code [http_status_code ]()
119
+ def http_error_from_status_code (http_status_code , http_url , description = None ):
120
+ return httpexceptions_by_code [http_status_code ](description )
119
121
120
122
121
123
def quoteattr (val ):
@@ -156,7 +158,66 @@ def invalid_argument(arg):
156
158
raise ValueError ("Unexpected query variable: %s" % quoteattr (arg ))
157
159
158
160
159
- def _create_and_expose_server (configuration ):
161
+ class ValidationReport (RuntimeError ):
162
+ def __init__ (self , errors_by_field ):
163
+ self .errors_by_field = errors_by_field
164
+
165
+ def serialize (self , output_format = 'text' ):
166
+ if output_format == 'json' :
167
+ return dict (errors = self .errors_by_field )
168
+ else :
169
+ lines = ["- %s: required %s" % (k , v ) for k , v in self .errors_by_field .items ()]
170
+ lines .insert (0 , '' )
171
+ return 'payload failed to validate:%s' % ('\n ' .join (lines ),)
172
+
173
+
174
+ def _is_not_none (value ):
175
+ """value is not None"""
176
+ return value is not None
177
+
178
+
179
+ def _is_string_and_non_empty (value ):
180
+ """value is a non-empty string"""
181
+ return isinstance (value , str ) and len (value ) > 0
182
+
183
+
184
+ _REQUEST_ARGS_POST_USER = namedtuple ('PostUserArgs' , [
185
+ 'full_name' ,
186
+ 'organization' ,
187
+ 'state' ,
188
+ 'country' ,
189
+ 'email' ,
190
+ 'comment' ,
191
+ 'password' ,
192
+ ])
193
+
194
+
195
+ _REQUEST_ARGS_POST_USER ._validators = defaultdict (lambda : _is_not_none , dict (
196
+ full_name = _is_string_and_non_empty ,
197
+ organization = _is_string_and_non_empty ,
198
+ state = _is_string_and_non_empty ,
199
+ country = _is_string_and_non_empty ,
200
+ email = _is_string_and_non_empty ,
201
+ comment = _is_string_and_non_empty ,
202
+ password = _is_string_and_non_empty ,
203
+ ))
204
+
205
+
206
+ def validate_payload (definition , payload ):
207
+ args = definition (* [payload .get (field , None ) for field in definition ._fields ])
208
+
209
+ errors_by_field = {}
210
+ for field_name , field_value in args ._asdict ().items ():
211
+ validator_fn = definition ._validators [field_name ]
212
+ if not validator_fn (field_value ):
213
+ errors_by_field [field_name ] = validator_fn .__doc__
214
+ if errors_by_field :
215
+ raise ValidationReport (errors_by_field )
216
+ else :
217
+ return args
218
+
219
+
220
+ def _create_and_expose_server (server , configuration ):
160
221
app = Flask ('coreapi' )
161
222
162
223
@app .get ('/user' )
@@ -171,9 +232,18 @@ def GET_user_username(username):
171
232
def POST_user ():
172
233
payload = request .get_json ()
173
234
174
- greeting = payload .get ('greeting' , '<none>' )
175
- if greeting == 'provocation' :
176
- raise http_error_from_status_code (422 , None )
235
+ try :
236
+ validated = validate_payload (_REQUEST_ARGS_POST_USER , payload )
237
+ except ValidationReport as vr :
238
+ return http_error_from_status_code (400 , None , vr .serialize ())
239
+
240
+ args = list (validated )
241
+
242
+ ret = createuser (configuration , args )
243
+ if ret != 0 :
244
+ raise http_error_from_status_code (400 , None )
245
+
246
+ greeting = 'hello client!'
177
247
return Response (greeting , 201 )
178
248
179
249
return app
0 commit comments