cyclid 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +174 -0
  3. data/README.md +54 -0
  4. data/app/cyclid.rb +61 -0
  5. data/app/cyclid/config.rb +38 -0
  6. data/app/cyclid/controllers.rb +123 -0
  7. data/app/cyclid/controllers/auth.rb +34 -0
  8. data/app/cyclid/controllers/auth/token.rb +78 -0
  9. data/app/cyclid/controllers/health.rb +96 -0
  10. data/app/cyclid/controllers/organizations.rb +104 -0
  11. data/app/cyclid/controllers/organizations/collection.rb +134 -0
  12. data/app/cyclid/controllers/organizations/config.rb +128 -0
  13. data/app/cyclid/controllers/organizations/document.rb +135 -0
  14. data/app/cyclid/controllers/organizations/job.rb +266 -0
  15. data/app/cyclid/controllers/organizations/members.rb +145 -0
  16. data/app/cyclid/controllers/organizations/stages.rb +251 -0
  17. data/app/cyclid/controllers/users.rb +47 -0
  18. data/app/cyclid/controllers/users/collection.rb +131 -0
  19. data/app/cyclid/controllers/users/document.rb +133 -0
  20. data/app/cyclid/health_helpers.rb +40 -0
  21. data/app/cyclid/job.rb +3 -0
  22. data/app/cyclid/job/helpers.rb +67 -0
  23. data/app/cyclid/job/job.rb +164 -0
  24. data/app/cyclid/job/runner.rb +275 -0
  25. data/app/cyclid/job/stage.rb +67 -0
  26. data/app/cyclid/log_buffer.rb +104 -0
  27. data/app/cyclid/models.rb +3 -0
  28. data/app/cyclid/models/job_record.rb +25 -0
  29. data/app/cyclid/models/organization.rb +64 -0
  30. data/app/cyclid/models/plugin_config.rb +25 -0
  31. data/app/cyclid/models/stage.rb +42 -0
  32. data/app/cyclid/models/step.rb +29 -0
  33. data/app/cyclid/models/user.rb +60 -0
  34. data/app/cyclid/models/user_permissions.rb +28 -0
  35. data/app/cyclid/monkey_patches.rb +37 -0
  36. data/app/cyclid/plugin_registry.rb +75 -0
  37. data/app/cyclid/plugins.rb +125 -0
  38. data/app/cyclid/plugins/action.rb +48 -0
  39. data/app/cyclid/plugins/action/command.rb +89 -0
  40. data/app/cyclid/plugins/action/email.rb +207 -0
  41. data/app/cyclid/plugins/action/email/html.erb +58 -0
  42. data/app/cyclid/plugins/action/email/text.erb +13 -0
  43. data/app/cyclid/plugins/action/script.rb +90 -0
  44. data/app/cyclid/plugins/action/slack.rb +129 -0
  45. data/app/cyclid/plugins/action/slack/note.erb +5 -0
  46. data/app/cyclid/plugins/api.rb +195 -0
  47. data/app/cyclid/plugins/api/github.rb +111 -0
  48. data/app/cyclid/plugins/api/github/callback.rb +66 -0
  49. data/app/cyclid/plugins/api/github/methods.rb +201 -0
  50. data/app/cyclid/plugins/api/github/status.rb +67 -0
  51. data/app/cyclid/plugins/builder.rb +80 -0
  52. data/app/cyclid/plugins/builder/mist.rb +107 -0
  53. data/app/cyclid/plugins/dispatcher.rb +89 -0
  54. data/app/cyclid/plugins/dispatcher/local.rb +167 -0
  55. data/app/cyclid/plugins/provisioner.rb +40 -0
  56. data/app/cyclid/plugins/provisioner/debian.rb +90 -0
  57. data/app/cyclid/plugins/provisioner/ubuntu.rb +98 -0
  58. data/app/cyclid/plugins/source.rb +39 -0
  59. data/app/cyclid/plugins/source/git.rb +64 -0
  60. data/app/cyclid/plugins/transport.rb +63 -0
  61. data/app/cyclid/plugins/transport/ssh.rb +155 -0
  62. data/app/cyclid/sinatra/api_helpers.rb +66 -0
  63. data/app/cyclid/sinatra/auth_helpers.rb +127 -0
  64. data/app/cyclid/sinatra/warden/strategies/api_token.rb +62 -0
  65. data/app/cyclid/sinatra/warden/strategies/basic.rb +58 -0
  66. data/app/cyclid/sinatra/warden/strategies/hmac.rb +76 -0
  67. data/app/db.rb +51 -0
  68. data/bin/cyclid-db-init +107 -0
  69. data/db/schema.rb +92 -0
  70. data/lib/cyclid/app.rb +4 -0
  71. metadata +407 -0
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # Top level module for all of the core Cyclid code.
17
+ module Cyclid
18
+ # Module for the Cyclid API
19
+ module API
20
+ # Module for all Organization related API endpoints
21
+ module Organizations
22
+ # API endpoints for Organization members
23
+ # @api REST
24
+ module Members
25
+ # @!group Organizations
26
+
27
+ # @!method get_organizations_organization_members_member
28
+ # @overload GET /organizations/:organization/members/:username
29
+ # @macro rest
30
+ # @param [String] organization Name of the organization.
31
+ # @param [String] username Username of the member.
32
+ # Get the details of the specified user within the organization.
33
+ # @return The requested member.
34
+ # @return [404] The organization or user does not exist, or the user is not a member of
35
+ # the organization.
36
+ # @example Get the 'user1' user from the 'example' organization
37
+ # GET /organizations/example/members/user1 => {"id": 1,
38
+ # "username": "user1",
39
+ # "email":"test@example.com",
40
+ # "permissions":{
41
+ # "admin":true,
42
+ # "write":true,
43
+ # "read":true
44
+ # }}
45
+
46
+ # @!method put_organizations_organization_members_member
47
+ # @overload PUT /organizations/:name/members/:username
48
+ # @macro rest
49
+ # @param [String] organization Name of the organization.
50
+ # @param [String] username Username of the member.
51
+ # Modify the permissions of specified user within the organization.
52
+ # @param [JSON] body User permissions.
53
+ # @option body [Hash] permissions Permissions to apply for the user.
54
+ # @return [200] The member was modified successfully.
55
+ # @return [404] The user does not exist, or is not a member of the organization.
56
+ # @example Give the member 'user1' write & read permissions for the 'example' organization
57
+ # PUT /organizations/example/members/user1 <= {"permissions": {
58
+ # "admin":false,
59
+ # "write":true,
60
+ # "read":true
61
+ # }}
62
+
63
+ # @!endgroup
64
+
65
+ # Sinatra callback
66
+ # @private
67
+ def self.registered(app)
68
+ include Errors::HTTPErrors
69
+
70
+ # Get the details of the specified user within the organization.
71
+ app.get '/:username' do
72
+ authorized_for!(params[:name], Operations::READ)
73
+
74
+ org = Organization.find_by(name: params[:name])
75
+ halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
76
+ if org.nil?
77
+
78
+ user = org.users.find_by(username: params[:username])
79
+ halt_with_json_response(404, INVALID_USER, 'user does not exist') \
80
+ if user.nil?
81
+
82
+ begin
83
+ perms = user.userpermissions.find_by(organization: org)
84
+
85
+ user_hash = user.serializable_hash
86
+ user_hash.delete_if do |key, _value|
87
+ key == 'password' || key == 'secret'
88
+ end
89
+
90
+ perms_hash = perms.serializable_hash
91
+ perms_hash.delete_if do |key, _value|
92
+ key == 'id' || key == 'user_id' || key == 'organization_id'
93
+ end
94
+
95
+ user_hash['permissions'] = perms_hash
96
+
97
+ return user_hash.to_json
98
+ rescue ActiveRecord::ActiveRecordError, \
99
+ ActiveRecord::UnknownAttributeError => ex
100
+
101
+ Cyclid.logger.debug ex.message
102
+ halt_with_json_response(500, INTERNAL_ERROR, ex.message)
103
+ end
104
+ end
105
+
106
+ # Modify the specified user within the organization.
107
+ app.put '/:username' do
108
+ authorized_for!(params[:name], Operations::WRITE)
109
+
110
+ payload = parse_request_body
111
+ Cyclid.logger.debug payload
112
+
113
+ org = Organization.find_by(name: params[:name])
114
+ halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
115
+ if org.nil?
116
+
117
+ user = org.users.find_by(username: params[:username])
118
+ halt_with_json_response(404, INVALID_USER, 'user does not exist') \
119
+ if user.nil?
120
+
121
+ begin
122
+ perms = user.userpermissions.find_by(organization: org)
123
+
124
+ payload_perms = payload['permissions'] if payload.key? 'permissions'
125
+ unless payload_perms.nil?
126
+ perms.admin = payload_perms['admin'] if payload_perms.key? 'admin'
127
+ perms.write = payload_perms['write'] if payload_perms.key? 'write'
128
+ perms.read = payload_perms['read'] if payload_perms.key? 'read'
129
+
130
+ Cyclid.logger.debug perms.serializable_hash
131
+
132
+ perms.save!
133
+ end
134
+ rescue ActiveRecord::ActiveRecordError, \
135
+ ActiveRecord::UnknownAttributeError => ex
136
+
137
+ Cyclid.logger.debug ex.message
138
+ halt_with_json_response(500, INTERNAL_ERROR, ex.message)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # Top level module for all of the core Cyclid code.
17
+ module Cyclid
18
+ # Module for the Cyclid API
19
+ module API
20
+ # Module for all Organization related API endpoints
21
+ module Organizations
22
+ # API endpoints for Organization Stages
23
+ # @api REST
24
+ module Stages
25
+ # @!group Organizations
26
+
27
+ # @!method get_organizations_organization_stages
28
+ # @overload GET /organizations/:organization/stages
29
+ # @macro rest
30
+ # @param [String] organization Name of the organization.
31
+ # Get all of the stages.
32
+ # @return All the stages within the organization.
33
+ # @return [404] The organization does not exist
34
+
35
+ # @!method post_organizations_organization_stages(body)
36
+ # @overload POST /organizations/:organization/stages
37
+ # @macro rest
38
+ # @param [String] organization Name of the organization.
39
+ # Create a new stage.
40
+ # @param [JSON] body New stage
41
+ # @option body [String] name Name of the new stage
42
+ # @option body [String] version (0.0.1) Version of the new stage
43
+ # @option body [Array<Object>] steps List of steps for the stage
44
+ # @return [400] The definition for the stage is invalid.
45
+ # @return [404] The organization does not exist.
46
+ # @return [409] A stage with the same name & version already exists.
47
+ # @example Create a simple stage for 'example' organization
48
+ # POST /organizations/example/stages <= {"name": "example",
49
+ # "steps": [
50
+ # {
51
+ # "action": "command",
52
+ # "cmd": "bundle install"
53
+ # }
54
+ # ]}
55
+
56
+ # @!method get_organizations_organization_stages_stage
57
+ # @overload GET /organizations/:organization/stages/:stage
58
+ # @macro rest
59
+ # @param [String] organization Name of the organization.
60
+ # @param [String] stage Name of the stage.
61
+ # Get the details of the specified stage within the organization. Returns every defined
62
+ # version for the given stage.
63
+ # @return The requested stage.
64
+ # @return [404] The organization or stage does not exist
65
+
66
+ # @!method get_organizations_organization_stages_stage_version
67
+ # @overload GET /organizations/:organization/stages/:stage/:version
68
+ # @macro rest
69
+ # @param [String] organization Name of the organization.
70
+ # @param [String] stage Name of the stage.
71
+ # @param [String] version Version of the stage.
72
+ # Get the details of the specified stage within the organization. Returns the specified
73
+ # version of the given stage.
74
+ # @return The requested stage.
75
+ # @return [404] The organization, stage or version of the stage does not exist
76
+
77
+ # @!endgroup
78
+
79
+ # Sinatra callback
80
+ # @private
81
+ def self.registered(app)
82
+ include Errors::HTTPErrors
83
+
84
+ # Get all of the stages.
85
+ app.get do
86
+ authorized_for!(params[:name], Operations::READ)
87
+
88
+ org = Organization.find_by(name: params[:name])
89
+ halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
90
+ if org.nil?
91
+
92
+ # Convert each Stage to a hash & sanitize it
93
+ stages = org.stages.all.map do |stage|
94
+ stage_hash = sanitize_stage(stage.serializable_hash)
95
+
96
+ # Santize each step in this stage
97
+ steps = stage.steps.map do |step|
98
+ sanitize_step(step.serializable_hash)
99
+ end
100
+ stage_hash['steps'] = steps
101
+
102
+ stage_hash
103
+ end
104
+
105
+ return stages.to_json
106
+ end
107
+
108
+ # Create a new stage.
109
+ app.post do
110
+ authorized_for!(params[:name], Operations::ADMIN)
111
+
112
+ payload = parse_request_body
113
+ Cyclid.logger.debug payload
114
+
115
+ org = Organization.find_by(name: params[:name])
116
+ halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
117
+ if org.nil?
118
+
119
+ halt_with_json_response(400, INVALID_JSON, 'stage does not define any steps') \
120
+ unless payload.key? 'steps'
121
+
122
+ # Ensure that a stage with the same name & version doesn't already exist
123
+ version = payload['version'] || '0.0.1'
124
+ halt_with_json_response(409, \
125
+ DUPLICATE, \
126
+ 'A stage with that name and version already exists') \
127
+ if org.stages.exists?(name: payload['name'], version: version)
128
+
129
+ begin
130
+ stage = Stage.new
131
+
132
+ stage.name = payload['name']
133
+ stage.version = payload['version'] if payload.key? 'version'
134
+ stage.organization = org
135
+
136
+ # Create the steps & store their serialized form
137
+ stage.steps << create_steps(payload['steps'])
138
+
139
+ stage.save!
140
+ rescue ActiveRecord::ActiveRecordError, \
141
+ ActiveRecord::UnknownAttributeError => ex
142
+
143
+ Cyclid.logger.debug ex.message
144
+ halt_with_json_response(400, INVALID_JSON, ex.message)
145
+ end
146
+ end
147
+
148
+ # Returns every defined version for the given stage.
149
+ app.get '/:stage' do
150
+ authorized_for!(params[:name], Operations::READ)
151
+
152
+ org = Organization.find_by(name: params[:name])
153
+ halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
154
+ if org.nil?
155
+
156
+ # There may be multiple versions of the same stage, so we need to
157
+ # find every instance of the given stage, convert each Stage to a
158
+ # hash & sanitize it
159
+ stages = org.stages.where(name: params[:stage]).map do |stage|
160
+ stage_hash = sanitize_stage(stage.serializable_hash)
161
+
162
+ # Santize each step in this stage
163
+ steps = stage.steps.map do |step|
164
+ sanitize_step(step.serializable_hash)
165
+ end
166
+ stage_hash['steps'] = steps
167
+
168
+ stage_hash
169
+ end
170
+
171
+ halt_with_json_response(404, INVALID_STAGE, 'stage does not exist') \
172
+ if stages.empty?
173
+
174
+ return stages.to_json
175
+ end
176
+
177
+ # Get the details of the specified stage within the organization.
178
+ # Returns the specified version of the given stage.
179
+ app.get '/:stage/:version' do
180
+ authorized_for!(params[:name], Operations::READ)
181
+
182
+ org = Organization.find_by(name: params[:name])
183
+ halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
184
+ if org.nil?
185
+
186
+ stage = org.stages.find_by(name: params[:stage], version: params[:version])
187
+ halt_with_json_response(404, INVALID_STAGE, 'stage does not exist') \
188
+ if stage.nil?
189
+
190
+ # Sanitize the stage
191
+ stage_hash = sanitize_stage(stage.serializable_hash)
192
+
193
+ # Santize each step in this stage
194
+ steps = stage.steps.map do |step|
195
+ sanitize_step(step.serializable_hash)
196
+ end
197
+ stage_hash['steps'] = steps
198
+
199
+ return stage_hash.to_json
200
+ end
201
+
202
+ app.helpers do
203
+ include Helpers
204
+ end
205
+ end
206
+
207
+ # Helpers for Stages
208
+ module Helpers
209
+ include Errors::HTTPErrors
210
+
211
+ # Create the serialized steps
212
+ #
213
+ # For each definition in the payload, inspect it and create the
214
+ # appropriate object for that action; that class is then serialized
215
+ # into JSON and stored in the Step in the database, and can then
216
+ # be unserialized back in to the desired object when it's needed
217
+ # without the database having to be aware of every single
218
+ # permutation of possible actions and arguments to them.
219
+ #
220
+ # @private
221
+ def create_steps(stage_steps)
222
+ sequence = 1
223
+ stage_steps.map do |stage_step|
224
+ step = Step.new
225
+ step.sequence = sequence
226
+
227
+ begin
228
+ action_name = stage_step['action']
229
+ plugin = Cyclid.plugins.find(action_name, Cyclid::API::Plugins::Action)
230
+
231
+ step_action = plugin.new(stage_step)
232
+ raise if step_action.nil?
233
+ rescue StandardError => ex
234
+ # XXX Rescue an internal exception
235
+ halt_with_json_response(404, INVALID_ACTION, ex.message)
236
+ end
237
+
238
+ # Serialize the object into the Step and store it in the database.
239
+ step.action = Oj.dump(step_action)
240
+ step.save!
241
+
242
+ sequence += 1
243
+
244
+ step
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require_rel 'users/*.rb'
17
+
18
+ # Top level module for all of the core Cyclid code.
19
+ module Cyclid
20
+ # Module for the Cyclid API
21
+ module API
22
+ # Controller for all User related API endpoints
23
+ class UserController < ControllerBase
24
+ helpers do
25
+ # Remove sensitive data from the users data
26
+ def sanitize_user(user)
27
+ user.delete_if do |key, _value|
28
+ key == 'password' || key == 'secret'
29
+ end
30
+ end
31
+ end
32
+
33
+ register Sinatra::Namespace
34
+
35
+ namespace '/users' do
36
+ register Users::Collection
37
+
38
+ namespace '/:username' do
39
+ register Users::Document
40
+ end
41
+ end
42
+ end
43
+
44
+ # Register this controller
45
+ Cyclid.controllers << UserController
46
+ end
47
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # Top level module for all of the core Cyclid code.
17
+ module Cyclid
18
+ # Module for the Cyclid API
19
+ module API
20
+ # Module for all User related API endpoints
21
+ module Users
22
+ # API endpoints for the User collection
23
+ # @api REST
24
+ module Collection
25
+ # @!group Users
26
+
27
+ # @!method users
28
+ # @overload GET /users
29
+ # @macro rest
30
+ # Get all of the users.
31
+ # @return List of users
32
+ # @example Get a list of users
33
+ # GET /users => [{
34
+ # "id": 1,
35
+ # "username": "user1",
36
+ # "email": "user1@example.com"
37
+ # },
38
+ # {
39
+ # "id": 2,
40
+ # "username": "user2",
41
+ # "email": "user2@example.com"
42
+ # }]
43
+ # @see get_users_user
44
+
45
+ # @!method post_users(body)
46
+ # @overload POST /users
47
+ # @macro rest
48
+ # Create a new user. Note that only *one* of 'password' or 'new_password' should be
49
+ # passed.
50
+ # @param [JSON] body New user
51
+ # @option body [String] username Username of the new user
52
+ # @option body [String] name Users real name
53
+ # @option body [String] email Users email address
54
+ # @option body [String] password Bcrypt2 encrypted password
55
+ # @option body [String] new_password Password in plain text, which will be encrypted
56
+ # before being stored in the databaase.
57
+ # @option body [String] secret HMAC signing secret. This should be a suitably long
58
+ # random string.
59
+ # @return [200] User was created successfully
60
+ # @return [400] The user definition is invalid
61
+ # @return [409] An user with that name already exists
62
+ # @example Create a new user with an encrypted password
63
+ # POST /users <= {"username": "user1",
64
+ # "email": "user1@example.com",
65
+ # "password": "<Bcrypt2 encrypted password>"}
66
+
67
+ # @!endgroup
68
+
69
+ # Sinatra callback
70
+ # @private
71
+ def self.registered(app)
72
+ include Errors::HTTPErrors
73
+
74
+ # @macro [attach] sinatra.get
75
+ # @overload get "$1"
76
+ # @method get_users
77
+ # @return [String] JSON represention of all of all the users.
78
+ # Get all of the users across all organizations.
79
+ app.get do
80
+ authorized_admin!(Operations::READ)
81
+
82
+ # Retrieve the user data in a form we can more easily manipulate so
83
+ # that we can sanitize it
84
+ users = User.all_as_hash
85
+
86
+ # Remove any sensitive data
87
+ users.map! do |user|
88
+ sanitize_user(user)
89
+ end
90
+
91
+ return users.to_json
92
+ end
93
+
94
+ # @macro [attach] sinatra.post
95
+ # @overload post "$1"
96
+ # @method post_users
97
+ # Create a new user.
98
+ app.post do
99
+ authorized_admin!(Operations::ADMIN)
100
+
101
+ payload = parse_request_body
102
+ Cyclid.logger.debug payload
103
+
104
+ begin
105
+ halt_with_json_response(409, \
106
+ DUPLICATE, \
107
+ 'a user with that name already exists') \
108
+ if User.exists?(username: payload['username'])
109
+
110
+ user = User.new
111
+ user.username = payload['username']
112
+ user.email = payload['email']
113
+ user.name = payload['name'] if payload.key? 'name'
114
+ user.password = payload['password'] if payload.key? 'password'
115
+ user.secret = payload['secret'] if payload.key? 'secret'
116
+ user.new_password = payload['new_password'] if payload.key? 'new_password'
117
+ user.save!
118
+ rescue ActiveRecord::ActiveRecordError, \
119
+ ActiveRecord::UnknownAttributeError => ex
120
+
121
+ Cyclid.logger.debug ex.message
122
+ halt_with_json_response(400, INVALID_JSON, ex.message)
123
+ end
124
+
125
+ return json_response(NO_ERROR, "user #{payload['username']} created")
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end