@@ -178,188 +178,205 @@ INPUT::
"group_name" : "<name>"
}
OUTPUT::
result : None if group not exist
{
"id" : "<id>",
"name" : "<name>",
"active": "<bool>",
"members" : [
{ "id" : "<userid>",
"username" : "<username>",
"firstname": "<firstname>",
"lastname" : "<lastname>",
"email" : "<email>",
"active" : "<bool>",
"admin" : "<bool>",
"ldap" : "<ldap_dn>"
},
…
]
error : null
create_users_group
------------------
Creates new users group. This command can be executed only using api_key
belonging to user with admin rights
INPUT::
api_key : "<api_key>"
method : "create_users_group"
args: {
"name": "<name>",
"active":"<bool> = True"
result: {
"id": "<newusersgroupid>",
"msg": "created new users group <name>"
error: null
add_user_to_users_groups
------------------------
add_user_to_users_group
-----------------------
Adds a user to a users group. This command can be executed only using api_key
method : "add_user_users_group"
"group_name" : "<groupname>",
"user_name" : "<username>"
"id": "<newusersgroupmemberid>",
"msg": "created new users group member"
get_repos
---------
Lists all existing repositories. This command can be executed only using api_key
method : "get_repos"
args: { }
result: [
"name" : "<name>"
"type" : "<type>",
"description" : "<description>"
get_repo
--------
Gets an existing repository. This command can be executed only using api_key
method : "get_repo"
result: None if repository not exist
"description" : "<description>",
"ldap" : "<ldap_dn>",
"permission" : "repository_(read|write|admin)"
"permission" : "repository.(read|write|admin)"
"id" : "<usersgroupid>",
"name" : "<usersgroupname>",
create_repo
-----------
Creates a repository. This command can be executed only using api_key
belonging to user with admin rights.
If repository name contains "/", all needed repository groups will be created.
For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
and create "baz" repository with "bar" as group.
method : "create_repo"
"owner_name" : "<ownername>",
"description" : "<description> = ''",
"repo_type" : "<type> = 'hg'",
"private" : "<bool> = False"
result: None
add_user_to_repo
----------------
Add a user to a repository. This command can be executed only using api_key
If "perm" is None, user will be removed from the repository.
method : "add_user_to_repo"
"repo_name" : "<reponame>",
"user_name" : "<username>",
"perm" : "(None|repository_(read|write|admin))",
"perm" : "(None|repository.(read|write|admin))",
add_users_group_to_repo
Add a users group to a repository. This command can be executed only using
api_key belonging to user with admin rights. If "perm" is None, group will
be removed from the repository.
method : "add_users_group_to_repo"
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
rhodecode.controllers.api
~~~~~~~~~~~~~~~~~~~~~~~~~
JSON RPC controller
:created_on: Aug 20, 2011
:author: marcink
:copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
:license: GPLv3, see COPYING for more details.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License or (at your opinion) any later version of the license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
import inspect
import logging
import types
import urllib
import traceback
from rhodecode.lib.compat import izip_longest, json
from paste.response import replace_header
from pylons.controllers import WSGIController
from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
HTTPBadRequest, HTTPError
from rhodecode.model.db import User
from rhodecode.lib.auth import AuthUser
log = logging.getLogger('JSONRPC')
class JSONRPCError(BaseException):
def __init__(self, message):
self.message = message
super(JSONRPCError, self).__init__()
def __str__(self):
return str(self.message)
def jsonrpc_error(message, code=None):
Generate a Response object with a JSON-RPC error body
from pylons.controllers.util import Response
resp = Response(body=json.dumps(dict(id=None, result=None, error=message)),
status=code,
content_type='application/json')
return resp
class JSONRPCController(WSGIController):
A WSGI-speaking JSON-RPC controller class
See the specification:
<http://json-rpc.org/wiki/specification>`.
Valid controller return values should be json-serializable objects.
Sub-classes should catch their exceptions and raise JSONRPCError
if they want to pass meaningful errors to the client.
def _get_method_args(self):
Return `self._rpc_args` to dispatched controller method
chosen by __call__
return self._rpc_args
def __call__(self, environ, start_response):
Parse the request body as JSON, look up the method on the
controller and if it exists, dispatch to it.
if 'CONTENT_LENGTH' not in environ:
log.debug("No Content-Length")
return jsonrpc_error(message="No Content-Length in request")
else:
length = environ['CONTENT_LENGTH'] or 0
length = int(environ['CONTENT_LENGTH'])
log.debug('Content-Length: %s', length)
if length == 0:
log.debug("Content-Length is 0")
return jsonrpc_error(message="Content-Length is 0")
raw_body = environ['wsgi.input'].read(length)
try:
json_body = json.loads(urllib.unquote_plus(raw_body))
except ValueError, e:
#catch JSON errors Here
# catch JSON errors Here
return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
% (e, urllib.unquote_plus(raw_body)))
# check AUTH based on API KEY
self._req_api_key = json_body['api_key']
self._req_id = json_body['id']
self._req_method = json_body['method']
self._req_params = json_body['args']
self._request_params = json_body['args']
log.debug('method: %s, params: %s',
self._req_method,
self._req_params)
self._request_params)
except KeyError, e:
return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
# check if we can find this session using api_key
u = User.get_by_api_key(self._req_api_key)
if u is None:
return jsonrpc_error(message='Invalid API KEY')
auth_u = AuthUser(u.user_id, self._req_api_key)
except Exception, e:
self._error = None
self._func = self._find_method()
except AttributeError, e:
return jsonrpc_error(message=str(e))
# now that we have a method, add self._req_params to
# self.kargs and dispatch control to WGIController
argspec = inspect.getargspec(self._func)
arglist = argspec[0][1:]
defaults = argspec[3] or []
defaults = map(type, argspec[3] or [])
default_empty = types.NotImplementedType
kwarglist = list(izip_longest(reversed(arglist), reversed(defaults),
fillvalue=default_empty))
# kw arguments required by this method
func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
# this is little trick to inject logged in user for
# perms decorators to work they expect the controller class to have
# rhodecode_user attribute set
self.rhodecode_user = auth_u
# This attribute will need to be first param of a method that uses
# api_key, which is translated to instance of user at that name
USER_SESSION_ATTR = 'apiuser'
if USER_SESSION_ATTR not in arglist:
return jsonrpc_error(message='This method [%s] does not support '
'authentication (missing %s param)' %
(self._func.__name__, USER_SESSION_ATTR))
# get our arglist and check if we provided them as args
for arg, default in kwarglist:
for arg, default in func_kwargs.iteritems():
if arg == USER_SESSION_ATTR:
# USER_SESSION_ATTR is something translated from api key and
# this is checked before so we don't need validate it
continue
# skip the required param check if it's default value is
# NotImplementedType (default_empty)
if not self._req_params or (type(default) == default_empty
and arg not in self._req_params):
return jsonrpc_error(message=('Missing non optional %s arg '
'in JSON DATA') % arg)
if (default == default_empty and arg not in self._request_params):
return jsonrpc_error(
message=(
'Missing non optional `%s` arg in JSON DATA' % arg
)
self._rpc_args = {USER_SESSION_ATTR:u}
self._rpc_args.update(self._req_params)
self._rpc_args = {USER_SESSION_ATTR: u}
self._rpc_args.update(self._request_params)
self._rpc_args['action'] = self._req_method
self._rpc_args['environ'] = environ
self._rpc_args['start_response'] = start_response
status = []
headers = []
exc_info = []
def change_content(new_status, new_headers, new_exc_info=None):
status.append(new_status)
headers.extend(new_headers)
exc_info.append(new_exc_info)
output = WSGIController.__call__(self, environ, change_content)
output = list(output)
headers.append(('Content-Length', str(len(output[0]))))
replace_header(headers, 'Content-Type', 'application/json')
start_response(status[0], headers, exc_info[0])
return output
def _dispatch_call(self):
Implement dispatch interface specified by WSGIController
raw_response = self._inspect_call(self._func)
if isinstance(raw_response, HTTPError):
self._error = str(raw_response)
except JSONRPCError, e:
self._error = str(e)
log.error('Encountered unhandled exception: %s' \
% traceback.format_exc())
json_exc = JSONRPCError('Internal server error')
self._error = str(json_exc)
if self._error is not None:
raw_response = None
response = dict(id=self._req_id, result=raw_response,
error=self._error)
return json.dumps(response)
except TypeError, e:
log.debug('Error encoding response: %s', e)
return json.dumps(dict(result=None,
error="Error encoding response"))
def _find_method(self):
Return method named by `self._req_method` in controller if able
log.debug('Trying to find JSON-RPC method: %s', self._req_method)
if self._req_method.startswith('_'):
@@ -15,355 +15,430 @@ from rhodecode.model.user import UserMod
from rhodecode.model.repo_permission import RepositoryPermissionModel
from rhodecode.model.users_group import UsersGroupModel
from rhodecode.model.repos_group import ReposGroupModel
log = logging.getLogger(__name__)
class ApiController(JSONRPCController):
API Controller
Each method needs to have USER as argument this is then based on given
API_KEY propagated as instance of user object
Preferably this should be first argument also
Each function should also **raise** JSONRPCError for any
errors that happens
@HasPermissionAllDecorator('hg.admin')
def pull(self, apiuser, repo):
Dispatch pull action on given repo
:param user:
:param repo:
if Repository.is_valid(repo) is False:
raise JSONRPCError('Unknown repo "%s"' % repo)
ScmModel().pull_changes(repo, self.rhodecode_user.username)
return 'Pulled from %s' % repo
except Exception:
raise JSONRPCError('Unable to pull changes from "%s"' % repo)
def get_user(self, apiuser, username):
""""
Get a user by username
:param apiuser
:param username
:param apiuser:
:param username:
user = User.get_by_username(username)
if not user:
return None
return dict(id=user.user_id,
return dict(
id=user.user_id,
username=user.username,
firstname=user.name,
lastname=user.lastname,
email=user.email,
active=user.active,
admin=user.admin,
ldap=user.ldap_dn
def get_users(self, apiuser):
Get all users
result = []
for user in User.getAll():
result.append(
dict(
ldap=user.ldap_dn)
result.append(dict(id=user.user_id,
ldap=user.ldap_dn))
return result
def create_user(self, apiuser, username, password, firstname,
lastname, email, active=True, admin=False, ldap_dn=None):
Create new user
:param password:
:param name:
:param lastname:
:param email:
:param active:
:param admin:
:param ldap_dn:
if User.get_by_username(username):
raise JSONRPCError("user %s already exist" % username)
UserModel().create_or_update(username, password, email, firstname,
lastname, active, admin, ldap_dn)
Session.commit()
return dict(msg='created new user %s' % username)
log.error(traceback.format_exc())
raise JSONRPCError('failed to create user %s' % username)
def get_users_group(self, apiuser, group_name):
Get users group by name
:param group_name
:param group_name:
users_group = UsersGroup.get_by_group_name(group_name)
if not users_group:
members = []
for user in users_group.members:
user = user.user
members.append(dict(id=user.user_id,
return dict(id=users_group.users_group_id,
name=users_group.users_group_name,
active=users_group.users_group_active,
members=members)
def get_users_groups(self, apiuser):
Get all users groups
for users_group in UsersGroup.getAll():
result.append(dict(id=users_group.users_group_id,
members=members))
def create_users_group(self, apiuser, name, active=True):
Creates an new usergroup
if self.get_users_group(apiuser, name):
raise JSONRPCError("users group %s already exist" % name)
ug = UsersGroupModel().create(name=name, active=active)
return dict(id=ug.users_group_id,
msg='created new users group %s' % name)
raise JSONRPCError('failed to create group %s' % name)
def add_user_to_users_group(self, apiuser, group_name, user_name):
Add a user to a group
:param user_name
:param user_name:
raise JSONRPCError('unknown users group %s' % group_name)
user = User.get_by_username(user_name)
except NoResultFound:
raise JSONRPCError('unknown user %s' % user_name)
ugm = UsersGroupModel().add_user_to_group(users_group, user)
return dict(id=ugm.users_group_member_id,
msg='created new users group member')
raise JSONRPCError('failed to create users group member')
@HasPermissionAnyDecorator('hg.admin')
def get_repo(self, apiuser, repo_name):
Get repository by name
:param repo_name
:param repo_name:
repo = Repository.get_by_repo_name(repo_name)
if repo is None:
raise JSONRPCError('unknown repository %s' % repo)
for user in repo.repo_to_perm:
perm = user.permission.permission_name
members.append(dict(type_="user",
ldap=user.ldap_dn,
permission=perm))
members.append(
type_="user",
permission=perm
for users_group in repo.users_group_to_perm:
perm = users_group.permission.permission_name
users_group = users_group.users_group
members.append(dict(type_="users_group",
id=users_group.users_group_id,
type_="users_group",
return dict(id=repo.repo_id,
name=repo.repo_name,
type=repo.repo_type,
description=repo.description,
id=repo.repo_id,
members=members
def get_repos(self, apiuser):
Get all repositories
for repository in Repository.getAll():
result.append(dict(id=repository.repo_id,
name=repository.repo_name,
type=repository.repo_type,
description=repository.description))
id=repository.repo_id,
description=repository.description
@HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
def create_repo(self, apiuser, name, owner_name, description='',
repo_type='hg', private=False):
Create a repository
:param name
:param description
:param type
:param private
:param owner_name
:param description:
:param type:
:param private:
:param owner_name:
owner = User.get_by_username(owner_name)
raise JSONRPCError('unknown user %s' % owner)
if self.get_repo(apiuser, name):
raise JSONRPCError("repo %s already exist" % name)
groups = name.split('/')
real_name = groups[-1]
groups = groups[:-1]
parent_id = None
for g in groups:
group = RepoGroup.get_by_group_name(g)
if not group:
group = ReposGroupModel().create(dict(group_name=g,
group_description='',
group_parent_id=parent_id))
group = ReposGroupModel().create(
group_name=g,
group_parent_id=parent_id
parent_id = group.group_id
RepoModel().create(dict(repo_name=real_name,
repo_name_full=name,
description=description,
private=private,
repo_type=repo_type,
repo_group=parent_id,
clone_uri=None), owner)
RepoModel().create(
repo_name=real_name,
clone_uri=None
),
owner
raise JSONRPCError('failed to create repository %s' % name)
def add_user_to_repo(self, apiuser, repo_name, user_name, perm):
Add permission for a user to a repository
:param perm
:param perm:
raise JSONRPCError('unknown user %s' % user)
RepositoryPermissionModel()\
.update_or_delete_user_permission(repo, user, perm)
msg='Added perm: %s for %s in repo: %s' % (
perm, user_name, repo_name
raise JSONRPCError('failed to edit permission %(repo)s for %(user)s'
% dict(user=user_name, repo=repo_name))
raise JSONRPCError(
'failed to edit permission %(repo)s for %(user)s' % dict(
user=user_name, repo=repo_name
def add_users_group_to_repo(self, apiuser, repo_name, group_name, perm):
Add permission for a users group to a repository
user_group = UsersGroup.get_by_group_name(group_name)
raise JSONRPCError('unknown users group %s' % user_group)
.update_or_delete_users_group_permission(repo, user_group,
perm)
perm, group_name, repo_name
'failed to edit permission %(repo)s for %(usergr)s' % dict(
usergr=group_name, repo=repo_name
rhodecode.model.users_group
~~~~~~~~~~~~~~~~~~~~~~~~~~~
repository permission model for RhodeCode
:created_on: Oct 1, 2011
:author: nvinot
:author: nvinot, marcink
:copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
:copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from rhodecode.model import BaseModel
from rhodecode.model.db import UserRepoToPerm, Permission
from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission
class RepositoryPermissionModel(BaseModel):
def get_user_permission(self, repository, user):
return UserRepoToPerm.query() \
.filter(UserRepoToPerm.user == user) \
.filter(UserRepoToPerm.repository == repository) \
.scalar()
def update_user_permission(self, repository, user, permission):
#TODO: REMOVE THIS !!
################################
import ipdb;ipdb.set_trace()
print 'setting ipdb debuggin for rhodecode.model.repo_permission.RepositoryPermissionModel.update_user_permission'
permission = Permission.get_by_key(permission)
current = self.get_user_permission(repository, user)
if current:
if not current.permission is permission:
current.permission = permission
p = UserRepoToPerm()
p.user = user
p.repository = repository
p.permission = permission
self.sa.add(p)
def delete_user_permission(self, repository, user):
self.sa.delete(current)
def get_users_group_permission(self, repository, users_group):
return UsersGroupRepoToPerm.query() \
.filter(UsersGroupRepoToPerm.users_group == users_group) \
.filter(UsersGroupRepoToPerm.repository == repository) \
def update_users_group_permission(self, repository, users_group,
permission):
current = self.get_users_group_permission(repository, users_group)
p = UsersGroupRepoToPerm()
p.users_group = users_group
def delete_users_group_permission(self, repository, users_group):
def update_or_delete_user_permission(self, repository, user, permission):
if permission:
self.update_user_permission(repository, user, permission)
self.delete_user_permission(repository, user)
def update_or_delete_users_group_permission(self, repository, user_group,
self.update_users_group_permission(repository, user_group,
permission)
self.delete_users_group_permission(repository, user_group)
Status change: