Changeset - 256e729a94cd
[Not reviewed]
beta
0 4 0
Marcin Kuzminski - 14 years ago 2011-09-30 17:03:20
marcin@python-works.com
Extended API

- updated docs
- created two new methods for creating users and creating users groups
- changed user attribute generated from api_key to apiuser for better name compatibility with functoin parameters
4 files changed with 160 insertions and 26 deletions:
0 comments (0 inline, 0 general)
docs/api/api.rst
Show inline comments
 
.. _api:
 

	
 

	
 
API
 
===
 

	
 

	
 
Starting from RhodeCode version 1.2 a simple API was implemented.
 
There's one schema for calling all api methods. API is implemented
 
There's a single schema for calling all api methods. API is implemented
 
with JSON protocol both ways. An url to send API request in RhodeCode is 
 
<your-server>/_admin/api
 
<your_server>/_admin/api
 

	
 

	
 
Clients need to send JSON data in such format::
 
All clients need to send JSON data in such format::
 

	
 
    {
 
        "api_key":"<api_key>",
 
        "method":"<method_name>",
 
        "args":{"<arg_key>":"<arg_val>"}
 
    }
 

	
 
Simply provide api_key for access and permission validation
 
method is name of method to call
 
and args is an key:value list of arguments to pass to method
 
Example call for autopulling remotes repos using curl::
 
    curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
 

	
 
Simply provide 
 
 - *api_key* for access and permission validation.
 
 - *method* is name of method to call
 
 - *args* is an key:value list of arguments to pass to method
 
    
 
.. note::
 
    
 
    api_key can be found in your user account page    
 
    
 
    
 
And will receive JSON formatted answer::
 
RhodeCode API will return always a JSON formatted answer::
 
    
 
    {
 
        "result": "<result>", 
 
        "error": null
 
    }
 

	
 
All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
 
calling api **error** key from response will contain failure description 
 
calling api *error* key from response will contain failure description 
 
and result will be null.
 

	
 
API METHODS
 
+++++++++++
 

	
 
    
 
pull
 
----
 

	
 
Pulls given repo from remote location. Can be used to automatically keep 
 
remote repos upto date. This command can be executed only using admin users
 
api_key
 
remote repos up to date. This command can be executed only using api_key 
 
belonging to user with admin rights
 

	
 
::
 
INPUT::
 

	
 
    api_key:"<api_key>"
 
    method: "pull"
 
    args: {"repo":<repo_name>}
 

	
 
OUTPUT::
 

	
 
    result:"Pulled from <repo_name>"
 
    error:null
 

	
 
    
 
create_user
 
-----------
 

	
 
Creates new user in RhodeCode. This command can be executed only using api_key 
 
belonging to user with admin rights
 

	
 
INPUT::
 

	
 
    api_key:"<api_key>"
 
    method: "create_user"
 
    args: {"username": "<username>", 
 
           "password": "<password>", 
 
           "active":   "<bool>", 
 
           "admin":    "<bool>", 
 
           "name":     "<firstname>", 
 
           "lastname": "<lastname>", 
 
           "email":    "<useremail>"}
 

	
 
OUTPUT::
 

	
 
    result:{"id": <newuserid>,
 
            "msg":"created new user <username>"}
 
    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_user"
 
    args: {"name":  "<groupname>", 
 
           "active":"<bool>"}
 

	
 
OUTPUT::
 

	
 
    result:{"id": <newusersgroupid>,
 
            "msg":"created new users group <groupname>"}
 
    error:null    
rhodecode/controllers/api/__init__.py
Show inline comments
 
@@ -127,43 +127,47 @@ class JSONRPCController(WSGIController):
 
        self._error = None
 
        try:
 
            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
 
        arglist = inspect.getargspec(self._func)[0][1:]
 

	
 
        # this is little trick to inject logged in user for 
 
        # perms decorators to work they expect the controller class to have
 
        # rhodecode_user set
 
        # rhodecode_user attribute set
 
        self.rhodecode_user = auth_u
 

	
 
        if 'user' not in arglist:
 
        # 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 user param)' %
 
                                 self._func.__name__)
 
                                 'authentication (missing %s param)' %
 
                                 (self._func.__name__, USER_SESSION_ATTR))
 

	
 
        # get our arglist and check if we provided them as args
 
        for arg in arglist:
 
            if arg == 'user':
 
                # user is something translated from api key and this is
 
                # checked before
 
            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
 

	
 
            if not self._req_params or arg not in self._req_params:
 
                return jsonrpc_error(message='Missing %s arg in JSON DATA' % arg)
 

	
 
        self._rpc_args = dict(user=u)
 
        self._rpc_args = {USER_SESSION_ATTR:u}
 
        self._rpc_args.update(self._req_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)
 
@@ -174,25 +178,24 @@ class JSONRPCController(WSGIController):
 
        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
 
        """
 
        try:
 
            raw_response = self._inspect_call(self._func)
 
            print raw_response
 
            if isinstance(raw_response, HTTPError):
 
                self._error = str(raw_response)
 
        except JSONRPCError as e:
 
            self._error = str(e)
 
        except Exception as e:
 
            log.debug('Encountered unhandled exception: %s', repr(e))
 
            json_exc = JSONRPCError('Internal server error')
 
            self._error = str(json_exc)
 

	
 
        if self._error is not None:
 
            raw_response = None
 

	
 
@@ -214,12 +217,13 @@ class JSONRPCController(WSGIController):
 
            raise AttributeError("Method not allowed")
 

	
 
        try:
 
            func = getattr(self, self._req_method, None)
 
        except UnicodeEncodeError:
 
            raise AttributeError("Problem decoding unicode in requested "
 
                                 "method name.")
 

	
 
        if isinstance(func, types.MethodType):
 
            return func
 
        else:
 
            raise AttributeError("No such method: %s" % self._req_method)
 

	
rhodecode/controllers/api/api.py
Show inline comments
 
import traceback
 
import logging
 

	
 
from rhodecode.controllers.api import JSONRPCController, JSONRPCError
 
from rhodecode.lib.auth import HasPermissionAllDecorator
 
from rhodecode.model.scm import ScmModel
 

	
 
from rhodecode.model.db import User, UsersGroup
 

	
 
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, user, repo):
 
    def pull(self, apiuser, repo):
 
        """
 
        Dispatch pull action on given repo
 
        
 
        
 
        param user:
 
        param repo:
 
        :param user:
 
        :param repo:
 
        """
 

	
 
        try:
 
            ScmModel().pull_changes(repo, self.rhodecode_user.username)
 
            return 'Pulled from %s' % repo
 
        except Exception:
 
            raise JSONRPCError('Unable to pull changes from "%s"' % repo)
 

	
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def create_user(self, apiuser, username, password, active, admin, name, 
 
                    lastname, email):
 
        """
 
        Creates new user
 
        
 
        :param apiuser:
 
        :param username:
 
        :param password:
 
        :param active:
 
        :param admin:
 
        :param name:
 
        :param lastname:
 
        :param email:
 
        """
 
        
 
        form_data = dict(username=username,
 
                         password=password,
 
                         active=active,
 
                         admin=admin,
 
                         name=name,
 
                         lastname=lastname,
 
                         email=email)
 
        try:
 
            u = User.create(form_data)
 
            return {'id':u.user_id,
 
                    'msg':'created new user %s' % name}
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to create user %s' % name)
 

	
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def create_users_group(self, apiuser, name, active):
 
        """
 
        Creates an new usergroup
 
        
 
        :param name:
 
        :param active:
 
        """
 
        form_data = {'users_group_name':name,
 
                     'users_group_active':active}
 
        try:
 
            ug = UsersGroup.create(form_data)
 
            return {'id':ug.users_group_id,
 
                    'msg':'created new users group %s' % name}
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to create group %s' % name)
 
        
 
\ No newline at end of file
rhodecode/model/db.py
Show inline comments
 
@@ -29,30 +29,31 @@ import datetime
 
import traceback
 
from datetime import date
 

	
 
from sqlalchemy import *
 
from sqlalchemy.exc import DatabaseError
 
from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
 
from sqlalchemy.orm.interfaces import MapperExtension
 

	
 
from beaker.cache import cache_region, region_invalidate
 

	
 
from vcs import get_backend
 
from vcs.utils.helpers import get_scm
 
from vcs.exceptions import RepositoryError, VCSError
 
from vcs.exceptions import VCSError
 
from vcs.utils.lazy import LazyProperty
 
from vcs.nodes import FileNode
 

	
 
from rhodecode.lib.exceptions import UsersGroupsAssignedException
 
from rhodecode.lib import str2bool, json, safe_str, get_changeset_safe
 
from rhodecode.lib import str2bool, json, safe_str, get_changeset_safe,\
 
    generate_api_key
 

	
 
from rhodecode.model.meta import Base, Session
 
from rhodecode.model.caching_query import FromCache
 

	
 
log = logging.getLogger(__name__)
 

	
 
#==============================================================================
 
# BASE CLASSES
 
#==============================================================================
 

	
 
class ModelSerializer(json.JSONEncoder):
 
    """
 
    Simple Serializer for JSON,
 
@@ -289,24 +290,43 @@ class User(Base, BaseModel):
 
    def get_by_api_key(cls, api_key):
 
        return Session.query(cls).filter(cls.api_key == api_key).one()
 

	
 

	
 
    def update_lastlogin(self):
 
        """Update user lastlogin"""
 

	
 
        self.last_login = datetime.datetime.now()
 
        Session.add(self)
 
        Session.commit()
 
        log.debug('updated user %s lastlogin', self.username)
 

	
 
    @classmethod
 
    def create(cls, form_data):
 
        from rhodecode.lib.auth import get_crypt_password
 
        
 
        try:
 
            new_user = cls()
 
            for k, v in form_data.items():
 
                if k == 'password':
 
                    v = get_crypt_password(v)
 
                setattr(new_user, k, v)
 

	
 
            new_user.api_key = generate_api_key(form_data['username'])
 
            Session.add(new_user)
 
            Session.commit()
 
            return new_user
 
        except:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 
            raise
 

	
 
class UserLog(Base, BaseModel):
 
    __tablename__ = 'user_logs'
 
    __table_args__ = {'extend_existing':True}
 
    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 
    repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
 

	
 
@@ -353,24 +373,25 @@ class UsersGroup(Base, BaseModel):
 
                                    "get_users_group_%s" % users_group_id))
 
        return users_group.get(users_group_id)
 

	
 
    @classmethod
 
    def create(cls, form_data):
 
        try:
 
            new_users_group = cls()
 
            for k, v in form_data.items():
 
                setattr(new_users_group, k, v)
 

	
 
            Session.add(new_users_group)
 
            Session.commit()
 
            return new_users_group
 
        except:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 
            raise
 

	
 
    @classmethod
 
    def update(cls, users_group_id, form_data):
 

	
 
        try:
 
            users_group = cls.get(users_group_id, cache=False)
 

	
 
            for k, v in form_data.items():
0 comments (0 inline, 0 general)