Show More
@@ -74,12 +74,44 b' INPUT::' | |||||
74 | api_key : "<api_key>" |
|
74 | api_key : "<api_key>" | |
75 | method : "pull" |
|
75 | method : "pull" | |
76 | args : { |
|
76 | args : { | |
77 |
"repo" : "<repo |
|
77 | "repo_name" : "<reponame>" | |
78 | } |
|
78 | } | |
79 |
|
79 | |||
80 | OUTPUT:: |
|
80 | OUTPUT:: | |
81 |
|
81 | |||
82 |
result : "Pulled from <repo |
|
82 | result : "Pulled from <reponame>" | |
|
83 | error : null | |||
|
84 | ||||
|
85 | ||||
|
86 | get_user | |||
|
87 | -------- | |||
|
88 | ||||
|
89 | Get's an user by username, Returns empty result if user is not found. | |||
|
90 | This command can be executed only using api_key belonging to user with admin | |||
|
91 | rights. | |||
|
92 | ||||
|
93 | INPUT:: | |||
|
94 | ||||
|
95 | api_key : "<api_key>" | |||
|
96 | method : "get_user" | |||
|
97 | args : { | |||
|
98 | "username" : "<username>" | |||
|
99 | } | |||
|
100 | ||||
|
101 | OUTPUT:: | |||
|
102 | ||||
|
103 | result: None if user does not exist or | |||
|
104 | { | |||
|
105 | "id" : "<id>", | |||
|
106 | "username" : "<username>", | |||
|
107 | "firstname": "<firstname>", | |||
|
108 | "lastname" : "<lastname>", | |||
|
109 | "email" : "<email>", | |||
|
110 | "active" : "<bool>", | |||
|
111 | "admin" :Β "<bool>", | |||
|
112 | "ldap" : "<ldap_dn>" | |||
|
113 | } | |||
|
114 | ||||
83 |
error |
|
115 | error: null | |
84 |
|
116 | |||
85 |
|
117 | |||
@@ -136,46 +168,11 b' INPUT::' | |||||
136 | OUTPUT:: |
|
168 | OUTPUT:: | |
137 |
|
169 | |||
138 | result: { |
|
170 | result: { | |
|
171 | "id" : "<new_user_id>", | |||
139 |
|
|
172 | "msg" : "created new user <username>" | |
140 | } |
|
173 | } | |
141 | error: null |
|
174 | error: null | |
142 |
|
175 | |||
143 | get_users_groups |
|
|||
144 | ---------------- |
|
|||
145 |
|
||||
146 | Lists all existing users groups. This command can be executed only using api_key |
|
|||
147 | belonging to user with admin rights. |
|
|||
148 |
|
||||
149 | INPUT:: |
|
|||
150 |
|
||||
151 | api_key : "<api_key>" |
|
|||
152 | method : "get_users_groups" |
|
|||
153 | args : { } |
|
|||
154 |
|
||||
155 | OUTPUT:: |
|
|||
156 |
|
||||
157 | result : [ |
|
|||
158 | { |
|
|||
159 | "id" : "<id>", |
|
|||
160 | "name" : "<name>", |
|
|||
161 | "active": "<bool>", |
|
|||
162 | "members" : [ |
|
|||
163 | { |
|
|||
164 | "id" : "<userid>", |
|
|||
165 | "username" : "<username>", |
|
|||
166 | "firstname": "<firstname>", |
|
|||
167 | "lastname" : "<lastname>", |
|
|||
168 | "email" : "<email>", |
|
|||
169 | "active" : "<bool>", |
|
|||
170 | "admin" :Β "<bool>", |
|
|||
171 | "ldap" : "<ldap_dn>" |
|
|||
172 | }, |
|
|||
173 | β¦ |
|
|||
174 | ] |
|
|||
175 | } |
|
|||
176 | ] |
|
|||
177 | error : null |
|
|||
178 |
|
||||
179 | get_users_group |
|
176 | get_users_group | |
180 | --------------- |
|
177 | --------------- | |
181 |
|
178 | |||
@@ -195,7 +192,7 b' OUTPUT::' | |||||
195 | result : None if group not exist |
|
192 | result : None if group not exist | |
196 | { |
|
193 | { | |
197 | "id" : "<id>", |
|
194 | "id" : "<id>", | |
198 |
"name" : |
|
195 | "group_name" : "<groupname>", | |
199 | "active": "<bool>", |
|
196 | "active": "<bool>", | |
200 | "members" : [ |
|
197 | "members" : [ | |
201 |
|
|
198 | { "id" : "<userid>", | |
@@ -212,6 +209,43 b' OUTPUT::' | |||||
212 |
|
|
209 | } | |
213 | error : null |
|
210 | error : null | |
214 |
|
|
211 | ||
|
212 | get_users_groups | |||
|
213 | ---------------- | |||
|
214 | ||||
|
215 | Lists all existing users groups. This command can be executed only using | |||
|
216 | api_key belonging to user with admin rights. | |||
|
217 | ||||
|
218 | INPUT:: | |||
|
219 | ||||
|
220 | api_key : "<api_key>" | |||
|
221 | method : "get_users_groups" | |||
|
222 | args : { } | |||
|
223 | ||||
|
224 | OUTPUT:: | |||
|
225 | ||||
|
226 | result : [ | |||
|
227 | { | |||
|
228 | "id" : "<id>", | |||
|
229 | "group_name" : "<groupname>", | |||
|
230 | "active": "<bool>", | |||
|
231 | "members" : [ | |||
|
232 | { | |||
|
233 | "id" : "<userid>", | |||
|
234 | "username" : "<username>", | |||
|
235 | "firstname": "<firstname>", | |||
|
236 | "lastname" : "<lastname>", | |||
|
237 | "email" : "<email>", | |||
|
238 | "active" : "<bool>", | |||
|
239 | "admin" :Β "<bool>", | |||
|
240 | "ldap" : "<ldap_dn>" | |||
|
241 | }, | |||
|
242 | β¦ | |||
|
243 | ] | |||
|
244 | } | |||
|
245 | ] | |||
|
246 | error : null | |||
|
247 | ||||
|
248 | ||||
215 | create_users_group |
|
249 | create_users_group | |
216 | ------------------ |
|
250 | ------------------ | |
217 |
|
251 | |||
@@ -223,7 +257,7 b' INPUT::' | |||||
223 | api_key : "<api_key>" |
|
257 | api_key : "<api_key>" | |
224 | method : "create_users_group" |
|
258 | method : "create_users_group" | |
225 | args: { |
|
259 | args: { | |
226 | "name": "<name>", |
|
260 | "group_name": "<groupname>", | |
227 | "active":"<bool> = True" |
|
261 | "active":"<bool> = True" | |
228 | } |
|
262 | } | |
229 |
|
263 | |||
@@ -231,7 +265,7 b' OUTPUT::' | |||||
231 |
|
265 | |||
232 | result: { |
|
266 | result: { | |
233 | "id": "<newusersgroupid>", |
|
267 | "id": "<newusersgroupid>", | |
234 | "msg": "created new users group <name>" |
|
268 | "msg": "created new users group <groupname>" | |
235 | } |
|
269 | } | |
236 | error: null |
|
270 | error: null | |
237 |
|
271 | |||
@@ -258,31 +292,6 b' OUTPUT::' | |||||
258 | } |
|
292 | } | |
259 | error: null |
|
293 | error: null | |
260 |
|
294 | |||
261 | get_repos |
|
|||
262 | --------- |
|
|||
263 |
|
||||
264 | Lists all existing repositories. This command can be executed only using api_key |
|
|||
265 | belonging to user with admin rights |
|
|||
266 |
|
||||
267 | INPUT:: |
|
|||
268 |
|
||||
269 | api_key : "<api_key>" |
|
|||
270 | method : "get_repos" |
|
|||
271 | args: { } |
|
|||
272 |
|
||||
273 | OUTPUT:: |
|
|||
274 |
|
||||
275 | result: [ |
|
|||
276 | { |
|
|||
277 | "id" : "<id>", |
|
|||
278 | "name" : "<name>" |
|
|||
279 | "type" : "<type>", |
|
|||
280 | "description" : "<description>" |
|
|||
281 | }, |
|
|||
282 | β¦ |
|
|||
283 | ] |
|
|||
284 | error: null |
|
|||
285 |
|
||||
286 | get_repo |
|
295 | get_repo | |
287 | -------- |
|
296 | -------- | |
288 |
|
297 | |||
@@ -294,15 +303,15 b' INPUT::' | |||||
294 | api_key : "<api_key>" |
|
303 | api_key : "<api_key>" | |
295 | method : "get_repo" |
|
304 | method : "get_repo" | |
296 | args: { |
|
305 | args: { | |
297 | "name" : "<name>" |
|
306 | "repo_name" : "<reponame>" | |
298 | } |
|
307 | } | |
299 |
|
308 | |||
300 | OUTPUT:: |
|
309 | OUTPUT:: | |
301 |
|
310 | |||
302 | result: None if repository not exist |
|
311 | result: None if repository does not exist or | |
303 | { |
|
312 | { | |
304 | "id" : "<id>", |
|
313 | "id" : "<id>", | |
305 |
"name" : |
|
314 | "repo_name" : "<reponame>" | |
306 | "type" : "<type>", |
|
315 | "type" : "<type>", | |
307 | "description" : "<description>", |
|
316 | "description" : "<description>", | |
308 | "members" : [ |
|
317 | "members" : [ | |
@@ -328,12 +337,38 b' OUTPUT::' | |||||
328 |
|
|
337 | } | |
329 | error: null |
|
338 | error: null | |
330 |
|
|
339 | ||
|
340 | get_repos | |||
|
341 | --------- | |||
|
342 | ||||
|
343 | Lists all existing repositories. This command can be executed only using api_key | |||
|
344 | belonging to user with admin rights | |||
|
345 | ||||
|
346 | INPUT:: | |||
|
347 | ||||
|
348 | api_key : "<api_key>" | |||
|
349 | method : "get_repos" | |||
|
350 | args: { } | |||
|
351 | ||||
|
352 | OUTPUT:: | |||
|
353 | ||||
|
354 | result: [ | |||
|
355 | { | |||
|
356 | "id" : "<id>", | |||
|
357 | "repo_name" : "<reponame>" | |||
|
358 | "type" : "<type>", | |||
|
359 | "description" : "<description>" | |||
|
360 | }, | |||
|
361 | β¦ | |||
|
362 | ] | |||
|
363 | error: null | |||
|
364 | ||||
|
365 | ||||
331 | get_repo_nodes |
|
366 | get_repo_nodes | |
332 | -------------- |
|
367 | -------------- | |
333 |
|
368 | |||
334 | returns a list of nodes and it's children in a flat list for a given path |
|
369 | returns a list of nodes and it's children in a flat list for a given path | |
335 | at given revision. It's possible to specify ret_type to show only files or |
|
370 | at given revision. It's possible to specify ret_type to show only `files` or | |
336 | dirs. This command can be executed only using api_key belonging to user |
|
371 | `dirs`. This command can be executed only using api_key belonging to user | |
337 | with admin rights |
|
372 | with admin rights | |
338 |
|
373 | |||
339 | INPUT:: |
|
374 | INPUT:: | |
@@ -341,7 +376,7 b' INPUT::' | |||||
341 | api_key : "<api_key>" |
|
376 | api_key : "<api_key>" | |
342 | method : "get_repo_nodes" |
|
377 | method : "get_repo_nodes" | |
343 | args: { |
|
378 | args: { | |
344 | "repo_name" : "<name>", |
|
379 | "repo_name" : "<reponame>", | |
345 | "revision" : "<revision>", |
|
380 | "revision" : "<revision>", | |
346 | "root_path" : "<root_path>", |
|
381 | "root_path" : "<root_path>", | |
347 | "ret_type" : "<ret_type>" = 'all' |
|
382 | "ret_type" : "<ret_type>" = 'all' | |
@@ -374,7 +409,7 b' INPUT::' | |||||
374 | api_key : "<api_key>" |
|
409 | api_key : "<api_key>" | |
375 | method : "create_repo" |
|
410 | method : "create_repo" | |
376 | args: { |
|
411 | args: { | |
377 |
"name" : |
|
412 | "repo_name" : "<reponame>", | |
378 | "owner_name" : "<ownername>", |
|
413 | "owner_name" : "<ownername>", | |
379 | "description" : "<description> = ''", |
|
414 | "description" : "<description> = ''", | |
380 | "repo_type" : "<type> = 'hg'", |
|
415 | "repo_type" : "<type> = 'hg'", | |
@@ -383,7 +418,10 b' INPUT::' | |||||
383 |
|
418 | |||
384 | OUTPUT:: |
|
419 | OUTPUT:: | |
385 |
|
420 | |||
386 |
result: |
|
421 | result: { | |
|
422 | "id": "<newrepoid>", | |||
|
423 | "msg": "Created new repository <reponame>", | |||
|
424 | } | |||
387 |
|
|
425 | error: null | |
388 |
|
426 | |||
389 | add_user_to_repo |
|
427 | add_user_to_repo | |
@@ -405,7 +443,9 b' INPUT::' | |||||
405 |
|
443 | |||
406 | OUTPUT:: |
|
444 | OUTPUT:: | |
407 |
|
445 | |||
408 |
result: |
|
446 | result: { | |
|
447 | "msg" : "Added perm: <perm> for <username> in repo: <reponame>" | |||
|
448 | } | |||
409 |
|
|
449 | error: null | |
410 |
|
450 | |||
411 | add_users_group_to_repo |
|
451 | add_users_group_to_repo | |
@@ -423,4 +463,10 b' INPUT::' | |||||
423 | "repo_name" : "<reponame>", |
|
463 | "repo_name" : "<reponame>", | |
424 |
"group_name" : |
|
464 | "group_name" : "<groupname>", | |
425 | "perm" : "(None|repository.(read|write|admin))", |
|
465 | "perm" : "(None|repository.(read|write|admin))", | |
426 |
|
|
466 | } | |
|
467 | OUTPUT:: | |||
|
468 | ||||
|
469 | result: { | |||
|
470 | "msg" : Added perm: <perm> for <groupname> in repo: <reponame>" | |||
|
471 | } | |||
|
472 |
@@ -64,23 +64,23 b' class ApiController(JSONRPCController):' | |||||
64 | """ |
|
64 | """ | |
65 |
|
65 | |||
66 | @HasPermissionAllDecorator('hg.admin') |
|
66 | @HasPermissionAllDecorator('hg.admin') | |
67 | def pull(self, apiuser, repo): |
|
67 | def pull(self, apiuser, repo_name): | |
68 | """ |
|
68 | """ | |
69 | Dispatch pull action on given repo |
|
69 | Dispatch pull action on given repo | |
70 |
|
70 | |||
71 |
|
71 | |||
72 | :param user: |
|
72 | :param user: | |
73 | :param repo: |
|
73 | :param repo_name: | |
74 | """ |
|
74 | """ | |
75 |
|
75 | |||
76 | if Repository.is_valid(repo) is False: |
|
76 | if Repository.is_valid(repo_name) is False: | |
77 | raise JSONRPCError('Unknown repo "%s"' % repo) |
|
77 | raise JSONRPCError('Unknown repo "%s"' % repo_name) | |
78 |
|
78 | |||
79 | try: |
|
79 | try: | |
80 | ScmModel().pull_changes(repo, self.rhodecode_user.username) |
|
80 | ScmModel().pull_changes(repo_name, self.rhodecode_user.username) | |
81 | return 'Pulled from %s' % repo |
|
81 | return 'Pulled from %s' % repo_name | |
82 | except Exception: |
|
82 | except Exception: | |
83 | raise JSONRPCError('Unable to pull changes from "%s"' % repo) |
|
83 | raise JSONRPCError('Unable to pull changes from "%s"' % repo_name) | |
84 |
|
84 | |||
85 | @HasPermissionAllDecorator('hg.admin') |
|
85 | @HasPermissionAllDecorator('hg.admin') | |
86 | def get_user(self, apiuser, username): |
|
86 | def get_user(self, apiuser, username): | |
@@ -151,10 +151,15 b' class ApiController(JSONRPCController):' | |||||
151 | raise JSONRPCError("user %s already exist" % username) |
|
151 | raise JSONRPCError("user %s already exist" % username) | |
152 |
|
152 | |||
153 | try: |
|
153 | try: | |
154 |
UserModel().create_or_update( |
|
154 | usr = UserModel().create_or_update( | |
155 | lastname, active, admin, ldap_dn) |
|
155 | username, password, email, firstname, | |
|
156 | lastname, active, admin, ldap_dn | |||
|
157 | ) | |||
156 | Session.commit() |
|
158 | Session.commit() | |
157 | return dict(msg='created new user %s' % username) |
|
159 | return dict( | |
|
160 | id=usr.user_id, | |||
|
161 | msg='created new user %s' % username | |||
|
162 | ) | |||
158 | except Exception: |
|
163 | except Exception: | |
159 | log.error(traceback.format_exc()) |
|
164 | log.error(traceback.format_exc()) | |
160 | raise JSONRPCError('failed to create user %s' % username) |
|
165 | raise JSONRPCError('failed to create user %s' % username) | |
@@ -185,7 +190,7 b' class ApiController(JSONRPCController):' | |||||
185 | ldap=user.ldap_dn)) |
|
190 | ldap=user.ldap_dn)) | |
186 |
|
191 | |||
187 | return dict(id=users_group.users_group_id, |
|
192 | return dict(id=users_group.users_group_id, | |
188 | name=users_group.users_group_name, |
|
193 | group_name=users_group.users_group_name, | |
189 | active=users_group.users_group_active, |
|
194 | active=users_group.users_group_active, | |
190 | members=members) |
|
195 | members=members) | |
191 |
|
196 | |||
@@ -212,31 +217,31 b' class ApiController(JSONRPCController):' | |||||
212 | ldap=user.ldap_dn)) |
|
217 | ldap=user.ldap_dn)) | |
213 |
|
218 | |||
214 | result.append(dict(id=users_group.users_group_id, |
|
219 | result.append(dict(id=users_group.users_group_id, | |
215 | name=users_group.users_group_name, |
|
220 | group_name=users_group.users_group_name, | |
216 | active=users_group.users_group_active, |
|
221 | active=users_group.users_group_active, | |
217 | members=members)) |
|
222 | members=members)) | |
218 | return result |
|
223 | return result | |
219 |
|
224 | |||
220 | @HasPermissionAllDecorator('hg.admin') |
|
225 | @HasPermissionAllDecorator('hg.admin') | |
221 | def create_users_group(self, apiuser, name, active=True): |
|
226 | def create_users_group(self, apiuser, group_name, active=True): | |
222 | """ |
|
227 | """ | |
223 | Creates an new usergroup |
|
228 | Creates an new usergroup | |
224 |
|
229 | |||
225 | :param name: |
|
230 | :param group_name: | |
226 | :param active: |
|
231 | :param active: | |
227 | """ |
|
232 | """ | |
228 |
|
233 | |||
229 | if self.get_users_group(apiuser, name): |
|
234 | if self.get_users_group(apiuser, group_name): | |
230 | raise JSONRPCError("users group %s already exist" % name) |
|
235 | raise JSONRPCError("users group %s already exist" % group_name) | |
231 |
|
236 | |||
232 | try: |
|
237 | try: | |
233 | ug = UsersGroupModel().create(name=name, active=active) |
|
238 | ug = UsersGroupModel().create(name=group_name, active=active) | |
234 | Session.commit() |
|
239 | Session.commit() | |
235 | return dict(id=ug.users_group_id, |
|
240 | return dict(id=ug.users_group_id, | |
236 | msg='created new users group %s' % name) |
|
241 | msg='created new users group %s' % group_name) | |
237 | except Exception: |
|
242 | except Exception: | |
238 | log.error(traceback.format_exc()) |
|
243 | log.error(traceback.format_exc()) | |
239 | raise JSONRPCError('failed to create group %s' % name) |
|
244 | raise JSONRPCError('failed to create group %s' % group_name) | |
240 |
|
245 | |||
241 | @HasPermissionAllDecorator('hg.admin') |
|
246 | @HasPermissionAllDecorator('hg.admin') | |
242 | def add_user_to_users_group(self, apiuser, group_name, username): |
|
247 | def add_user_to_users_group(self, apiuser, group_name, username): | |
@@ -312,7 +317,7 b' class ApiController(JSONRPCController):' | |||||
312 |
|
317 | |||
313 | return dict( |
|
318 | return dict( | |
314 | id=repo.repo_id, |
|
319 | id=repo.repo_id, | |
315 | name=repo.repo_name, |
|
320 | repo_name=repo.repo_name, | |
316 | type=repo.repo_type, |
|
321 | type=repo.repo_type, | |
317 | description=repo.description, |
|
322 | description=repo.description, | |
318 | members=members |
|
323 | members=members | |
@@ -331,7 +336,7 b' class ApiController(JSONRPCController):' | |||||
331 | result.append( |
|
336 | result.append( | |
332 | dict( |
|
337 | dict( | |
333 | id=repository.repo_id, |
|
338 | id=repository.repo_id, | |
334 | name=repository.repo_name, |
|
339 | repo_name=repository.repo_name, | |
335 | type=repository.repo_type, |
|
340 | type=repository.repo_type, | |
336 | description=repository.description |
|
341 | description=repository.description | |
337 | ) |
|
342 | ) | |
@@ -367,13 +372,13 b' class ApiController(JSONRPCController):' | |||||
367 | raise JSONRPCError(e) |
|
372 | raise JSONRPCError(e) | |
368 |
|
373 | |||
369 | @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') |
|
374 | @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') | |
370 | def create_repo(self, apiuser, name, owner_name, description='', |
|
375 | def create_repo(self, apiuser, repo_name, owner_name, description='', | |
371 | repo_type='hg', private=False): |
|
376 | repo_type='hg', private=False): | |
372 | """ |
|
377 | """ | |
373 | Create a repository |
|
378 | Create a repository | |
374 |
|
379 | |||
375 | :param apiuser: |
|
380 | :param apiuser: | |
376 | :param name: |
|
381 | :param repo_name: | |
377 | :param description: |
|
382 | :param description: | |
378 | :param type: |
|
383 | :param type: | |
379 | :param private: |
|
384 | :param private: | |
@@ -386,10 +391,10 b' class ApiController(JSONRPCController):' | |||||
386 | except NoResultFound: |
|
391 | except NoResultFound: | |
387 | raise JSONRPCError('unknown user %s' % owner) |
|
392 | raise JSONRPCError('unknown user %s' % owner) | |
388 |
|
393 | |||
389 |
if |
|
394 | if Repository.get_by_repo_name(repo_name): | |
390 | raise JSONRPCError("repo %s already exist" % name) |
|
395 | raise JSONRPCError("repo %s already exist" % repo_name) | |
391 |
|
396 | |||
392 | groups = name.split('/') |
|
397 | groups = repo_name.split('/') | |
393 | real_name = groups[-1] |
|
398 | real_name = groups[-1] | |
394 | groups = groups[:-1] |
|
399 | groups = groups[:-1] | |
395 | parent_id = None |
|
400 | parent_id = None | |
@@ -405,10 +410,10 b' class ApiController(JSONRPCController):' | |||||
405 | ) |
|
410 | ) | |
406 | parent_id = group.group_id |
|
411 | parent_id = group.group_id | |
407 |
|
412 | |||
408 | RepoModel().create( |
|
413 | repo = RepoModel().create( | |
409 | dict( |
|
414 | dict( | |
410 | repo_name=real_name, |
|
415 | repo_name=real_name, | |
411 | repo_name_full=name, |
|
416 | repo_name_full=repo_name, | |
412 | description=description, |
|
417 | description=description, | |
413 | private=private, |
|
418 | private=private, | |
414 | repo_type=repo_type, |
|
419 | repo_type=repo_type, | |
@@ -418,9 +423,15 b' class ApiController(JSONRPCController):' | |||||
418 | owner |
|
423 | owner | |
419 | ) |
|
424 | ) | |
420 | Session.commit() |
|
425 | Session.commit() | |
|
426 | ||||
|
427 | return dict( | |||
|
428 | id=repo.repo_id, | |||
|
429 | msg="Created new repository %s" % repo.repo_name | |||
|
430 | ) | |||
|
431 | ||||
421 | except Exception: |
|
432 | except Exception: | |
422 | log.error(traceback.format_exc()) |
|
433 | log.error(traceback.format_exc()) | |
423 | raise JSONRPCError('failed to create repository %s' % name) |
|
434 | raise JSONRPCError('failed to create repository %s' % repo_name) | |
424 |
|
435 | |||
425 | @HasPermissionAnyDecorator('hg.admin') |
|
436 | @HasPermissionAnyDecorator('hg.admin') | |
426 | def add_user_to_repo(self, apiuser, repo_name, username, perm): |
|
437 | def add_user_to_repo(self, apiuser, repo_name, username, perm): |
General Comments 0
You need to be logged in to leave comments.
Login now