Пример #1
0
    def get(cls, request, pk=None, *args, **kwargs):
        """
        Fetches all instances of that class, or a specific instance
        if pk is given.

        :param request: client request
        :type request: raccoon.utils.request.Request
        :param pk: primary key of an instance
        :param args: not used
        :param kwargs: not used
        :return: None
        """
        if not cls.model:
            raise ReplyError(404)

        if pk:
            try:
                response = cls.model.objects.get(id=pk)
            except DoesNotExist:
                raise ReplyError(404)

            response = response.get_dict()
        else:
            response = cls.model.objects.all()
            response = [r.get_dict() for r in response]

        # get the rights of the user
        rights = []
        for right_id in request.user.rights:
            try:
                rights.append(Right.objects.get(pk=right_id))
            except:
                pass

        request.send(cls.filter_response(response, rights))
Пример #2
0
    def post(cls, request, method=None, flow_id=None, job_id=None, *args, **kwargs):
        job = None
        flow = None
        if flow_id:
            try:
                flow = Flow.objects.get(id=flow_id)
            except DoesNotExist:
                raise ReplyError(422)

            connector = flow.job.connector
        else:
            connector = Connector.objects.filter(type='jenkins').first()
            if not connector:
                raise ReplyError(422)

        if job_id:
            try:
                job = Job.objects.get(id=job_id)
            except DoesNotExist:
                raise ReplyError(422)

        # create Jenkins interface
        jenkins = JenkinsInterface(connector)
        method = getattr(jenkins, method, None)
        if not method:
            raise ReplyError(404)

        response = yield method(
            request=request, flow=flow, job=job, *args, **kwargs
        )
        request.send(response)
Пример #3
0
    def post(cls, request, email=None, password=None, **kwargs):
        """
        Authenticates a user given email and password.

        If LDAP_AUTH is enabled in Raccoon settings, then the LDAP server
        will be queried to check the user credentials.
        Else, the user credentials are checked against the database and the
        password encrypted with bcrypt.

        :param request: client request
        :param email: user credential email
        :param password: user credential password
        :param kwargs: not used
        :return: None
        """
        if not email or not password:
            raise ReplyError(400, 'Invalid email or password')

        if LDAP_AUTH:
            try:
                server = Server(LDAP_CONF['uri'],
                                port=LDAP_CONF['port'],
                                get_info=ALL)
                with Connection(server,
                                authentication=AUTH_SIMPLE,
                                user=email,
                                password=password,
                                auto_bind=True) as conn:
                    conn.search(search_base='DC=ad,DC=avira,DC=com',
                                search_filter='(userPrincipalName=%s)' % email,
                                attributes=['displayName'])
                    name = str(conn.entries[0].displayName)
            except LDAPBindError:
                raise ReplyError(404, 'LDAP invalid credentials')
            except:
                raise ReplyError(500, 'LDAP server error')

            try:
                user = cls.model.objects.get(email=email)
            except DoesNotExist:
                user = cls.model.objects.create(name=name,
                                                email=email,
                                                active_directory=True)
        else:
            password = password.encode('utf-8')
            try:
                user = cls.model.objects.get(email=email)
            except DoesNotExist:
                raise ReplyError(404, 'Invalid email or password!')

            if not bcrypt.checkpw(password, user.password.encode('utf-8')):
                raise ReplyError(404, 'Invalid email or password')

        token = jwt.encode({
            'id': str(user.pk),
            'role': user.role
        },
                           SECRET,
                           algorithm='HS256')
        request.send({'token': token.decode('utf8'), 'userId': str(user.pk)})
Пример #4
0
    def post(cls, request, method=None, connectorId=None, *args, **kwargs):
        try:
            connector = Connector.objects.get(id=connectorId)
        except DoesNotExist:
            raise ReplyError(404)

        salt_interface = SaltStackInterface(connector)
        method = getattr(salt_interface, method, None)

        if not method:
            raise ReplyError(404)

        response = yield method(**kwargs)

        user = request.user
        audit_log = AuditLog(user=user.email,
                             action=kwargs.get('fun'),
                             project=kwargs.get('service_type'),
                             environment=kwargs.get('target_env'),
                             message="Salt master operation")
        audit_log.save()

        request.broadcast(audit_log.get_dict(),
                          verb="post",
                          resource="/api/v1/auditlogs/",
                          admin_only=True)

        request.send(response)
Пример #5
0
    def post(cls, request, *args, **kwargs):
        """
        Creates a new instance of the class, based on the body
        received from the client.

        If audit_logs is enabled on the class, then a log item will be created
        for this action.

        Broadcasts the action to all connected users.

        :param request: client request
        :type request: raccoon.utils.request.Request
        :param args: not used
        :param kwargs: instance body
        :return: None
        """
        if not cls.model:
            raise ReplyError(404)

        params = {}
        for key, value in kwargs.items():
            if value and hasattr(cls.model, key):
                # !important
                # Make value = ObjectId(value) if field is a reference field
                field = cls.model._fields.get(key)
                if type(field) is ReferenceField:
                    if not value:
                        value = None
                    else:
                        model_name = field.document_type._get_collection().name
                        value = DBRef(model_name, ObjectId(value))

                params[key] = value

        try:
            response = cls.model.objects.create(**params)
        except NotUniqueError as e:
            log.info("Failed to create {}".format(cls.model.__name__),
                     exc_info=True)
            raise ReplyError(409, cls.model.get_message_from_exception(e))
        except InvalidDocumentError as e:
            log.info("Invalid document {}".format(cls.model.__name__),
                     exc_info=True)
            raise ReplyError(400, cls.model.get_message_from_exception(e))

        if cls.audit_logs:
            user = request.user
            audit_log = AuditLog(user=user.email,
                                 action='new {}'.format(cls.model.__name__),
                                 message='{} {} added'.format(
                                     cls.model.__name__, kwargs.get('name')))
            audit_log.save()

            request.broadcast(audit_log.get_dict(),
                              verb='post',
                              resource='/api/v1/auditlogs/',
                              admin_only=True)

        request.broadcast(response.get_dict())
Пример #6
0
    def fetch(self,
              url,
              method='GET',
              body=None,
              headers=None,
              timeout=15,
              follow_redirects=True,
              auth_username=None,
              auth_password=None,
              connection_timeout=5):
        """
        Perform and asynchronous HTTP request, deserialize the response
        body as JSON and return tuple of body and headers.

        :param url: HTTP url
        :param method: HTTP method
        :param body: HTTP body
        :param headers: HTTP headers
        :param timeout: request timeout
        :param follow_redirects: request follow redirects
        :param auth_username: Authentication username
        :param auth_password: Authentication password
        :param connection_timeout: Number of seconds to wait for a connection
        :return: tuple of JSON body and headers
        :rtype: tuple
        """
        body = body or 'no body' if method.upper() == 'POST' else None

        try:
            response = yield self.HTTPClient.fetch(
                HTTPRequest(url=url,
                            method=method,
                            body=body,
                            headers=headers,
                            follow_redirects=follow_redirects,
                            use_gzip=True,
                            validate_cert=False,
                            auth_username=auth_username,
                            auth_password=auth_password,
                            request_timeout=timeout,
                            connect_timeout=connection_timeout))
        except HTTPError as exc:
            raise ReplyError(exc.code, str(exc))
        except Exception as exc:
            log.error(exc)
            raise ReplyError(500, str(exc))
        else:
            body = response.body
            headers = response.headers

            content_type = headers.get('Content-Type')
            if content_type and 'application/json' in content_type:
                body = json.loads(response.body.decode('utf-8'))

            raise gen.Return((body, headers))
Пример #7
0
    def get(cls, request, method=None, *args, **kwargs):
        connector = Connector.objects.filter(type='jenkins').first()
        if not connector:
            raise ReplyError(422)

        # create Jenkins interface
        jenkins = JenkinsInterface(connector)
        method = getattr(jenkins, method, None)
        if not method:
            raise ReplyError(404)

        response = yield method(request=request, *args, **kwargs)
        request.send(response)
Пример #8
0
    def get(cls, request, method=None, project=None, *args, **kwargs):
        try:
            project = Project.objects.get(id=project)
        except DoesNotExist:
            raise ReplyError(422)

        bitbucketserver = BitbucketServerInterface(connector=project.connector)
        method = getattr(bitbucketserver, method, None)
        if not method:
            raise ReplyError(404)

        response = yield method(project=project, **kwargs)

        request.send(response)
Пример #9
0
    def delete(cls, request, pk):
        """
        Deletes an instance of the class, identified by its primary key.

        If audit_logs is enabled on the class, then a log item will be created
        for this action.

        Broadcasts the action to all connected users.

        :param request: client request
        :type request: raccoon.utils.request.Request
        :param pk: primary key
        :return: None
        """
        if not cls.model:
            raise ReplyError(404)

        if not pk:
            raise ReplyError(400)

        try:
            instance = cls.model.objects.get(id=pk)
        except DoesNotExist:
            raise ReplyError(404)

        try:
            instance.delete()
        except NotUniqueError as e:
            log.info("Not unique error", exc_info=True)
            raise ReplyError(409, cls.model.get_message_from_exception(e))
        except InvalidDocumentError as e:
            log.info("Invalid document error", exc_info=True)
            raise ReplyError(400, cls.model.get_message_from_exception(e))

        if cls.audit_logs:
            user = request.user
            audit_log = AuditLog(user=user.email,
                                 action='delete {}'.format(cls.model.__name__),
                                 message='{} {} deleted'.format(
                                     cls.model.__name__,
                                     getattr(instance, 'name', '')))
            audit_log.save()

            request.broadcast(audit_log.get_dict(),
                              verb='post',
                              resource='/api/v1/auditlogs/',
                              admin_only=True)

        request.broadcast(pk)
Пример #10
0
    def get(cls, request, method=None, project=None, *args, **kwargs):
        try:
            project = Project.objects.get(id=project)
        except DoesNotExist:
            raise ReplyError(422)

        # create GitHub interface & select operation
        bitbucket = BitbucketInterface(connector=project.connector)
        method = getattr(bitbucket, method, None)
        if not method:
            raise ReplyError(404)

        response = yield method(project=project)

        request.send(response)
Пример #11
0
    def get(cls, url):
        for url_regex, handler in cls.urlpatterns:
            match = re.match(url_regex, url)
            if match:
                return handler, match.groupdict()

        raise ReplyError(404)
Пример #12
0
    def post(cls, request, *args, **kwargs):
        project_id = kwargs.get('project')
        branch_name = kwargs.get('branch')

        # get project
        try:
            project = Project.objects.get(id=project_id)
        except DoesNotExist:
            raise ReplyError(400)

        # connect to github
        github = GitHubInterface(project.connector)

        # get commits and create changelog
        commits = yield github.commits(project=project, branch=branch_name)
        changelog = []
        for item in commits:
            changelog.append({
                'message': item['commit']['message'],
                'date': item['commit']['committer']['date'],
                'url': item['html_url'],
                'author': {
                    'name': item['commit']['committer']['name'],
                    'email': item['commit']['committer']['email'],
                }
            })

        # pass changelog
        kwargs['changelog'] = changelog
        super().post(request, *args, **kwargs)
Пример #13
0
    def trigger(self, request, flow, callback_method=None, *args, **kwargs):
        """
        Triggers the job in Jenkins by collecting the job name from flow
        and job arguments from the context generated by that flow.
        Builds the URL for the Jenkins API and performs the HTTP call,
        creates and starts the watcher tasks for the current job.

        If everything works OK, the client receives a 201 HTTP code.

        :param request: HTTP request
        :type request: raccoon.utils.request.Request
        :param flow: Flow
        :type flow: raccoon.models.flow.Flow
        :param callback_method: callback method assigned to Task
        :param kwargs: parameters for jenkins job
        """
        # Get the verb and path for this job, according to Jenkins API URLs
        verb, path = URLS.get('build')
        if kwargs:
            verb, path = URLS.get('build_with_params')

        # Create URL with parameters
        job_name = flow.job.job
        query = urlencode(kwargs.get('job_arguments'))
        url = urljoin(self.api_url, path).format(job_name=job_name, )
        url = '{}?{}'.format(url, query)

        # Call Jenkins API to schedule the task
        body, headers = yield self.fetch(
            method=verb,
            url=url,
        )

        task = Task(connector_type='jenkins',
                    action_type=flow.job.action_type,
                    user=request.user,
                    job=flow.job,
                    context=kwargs,
                    status=PENDING,
                    date_added=datetime.datetime.utcnow(),
                    environment=kwargs.get('environment', {}).get('id'),
                    project=kwargs.get('project', {}).get('id'))
        task.add_callback(callback_method)
        task.save()

        # Start local Jenkins watcher jobs
        # Get queue URL from Jenkins response headers
        queue_url = headers.get('Location')
        job_watcher = JenkinsQueueWatcherTask(task,
                                              countdown=5,
                                              api_url=self.api_url,
                                              queue_url=queue_url)
        yield job_watcher.delay()

        # broadcast the Task object to all connected clients
        request.broadcast(task.get_dict(),
                          verb='post',
                          resource='/api/v1/tasks/')

        raise ReplyError(201)
Пример #14
0
    def get(cls, request, pk=None, *args, **kwargs):
        """
        Fetches and returns audit logs for the past 3 days.
        If pk is specified, then the result will be the audit log
        associated to that primary key.

        :param request: http request
        :param pk: audit log primary key
        :param args: not used
        :param kwargs: not used
        :return: audit logs
        """
        if pk:
            try:
                response = cls.model.objects.get(id=pk)
            except DoesNotExist:
                raise ReplyError(404)

            response = response.get_dict()
        else:
            response = cls.model.objects.filter(
                date_added__gte=(
                    datetime.datetime.utcnow() - datetime.timedelta(days=3)
                )
            ).order_by('+date_added').all()
            response = [r.get_dict() for r in response]

        request.send(response)
Пример #15
0
    def get(cls, request, pk=None, project=None, *args, **kwargs):
        """
        Fetches all builds, or a specific instance if pk is given.

        :param request: client request
        :type request: raccoon.utils.request.Request
        :param pk: primary key of an instance
        :param project: project id to be used in filter
        :param args: not used
        :param kwargs: not used
        :return: None
        """
        if pk:
            try:
                response = Build.objects.get(id=pk)
            except DoesNotExist:
                raise ReplyError(404)

            response = response.get_dict()
        else:
            query_kw = {}
            if project:
                query_kw['project'] = project

            response = cls.model.objects(
                **query_kw).order_by('-date_added')[:cls.page_size]
            response = [r.get_dict() for r in response]

        request.send(response)
Пример #16
0
    def filter_response(cls, response, rights):
        """
        Filters the response based on the assigned rights of the user
        
        :param response:
        :param rights:
        :return:
        """
        if not rights:
            return response

        # treat rights if there is only one entity in the response
        if type(response) is dict:
            if cls.check_rights(response, rights):
                return response
            else:
                raise ReplyError(404)

        result = list()
        # filter entities that should be returned
        for entity in response:
            if cls.check_rights(entity, rights):
                result.append(entity)

        return result
Пример #17
0
    def install_callback(cls, task, response):
        """
        Passed to the Task object, this function represents the callback
        that will be executed when the task finishes successfully.
        Creates the Install object and broadcasts the change to all
        connected clients.

        :param task: Task object that was passed this callback.
        :type task: raccoon.models.task.Task
        :param response: The result of the task.
        :return: None
        """
        project_id = task.context.get('project', {}).get('id')
        build_id = task.context.get('build_id')
        env_id = task.context.get('environment', {}).get('id')

        # Get Project, Build and Environment to create the Install object
        try:
            project = Project.objects.get(id=project_id)
        except DoesNotExist:
            raise ReplyError(404)

        try:
            build = Build.objects.get(id=build_id)
        except DoesNotExist:
            raise ReplyError(404)

        try:
            env = Environment.objects.get(id=env_id)
        except DoesNotExist:
            raise ReplyError(404)

        install = Install(build=build,
                          project=project,
                          environment=env,
                          task=task)
        install.save()

        # Notify clients about the new Install
        broadcast(install.get_dict(),
                  verb='post',
                  resource='/api/v1/installs/')
Пример #18
0
    def stop(self, request, task_id, flow=None, job=None, *args, **kwargs):
        verb, path = URLS.get('stop')
        url = urljoin(self.api_url, path).format(job_name=job.job, **kwargs)

        try:
            task = Task.objects.get(id=task_id)
        except DoesNotExist:
            raise ReplyError(404)

        if (not request.is_admin
                and request.user_data.get('id') != str(task.user.id)):
            raise ReplyError(401)

        response, headers = yield self.fetch(
            method=verb,
            url=url,
            follow_redirects=False,
        )

        raise gen.Return(response)
Пример #19
0
    def post(cls, request, *args, **kwargs):
        if LDAP_AUTH:
            raise ReplyError(403)

        if not cls.model:
            raise ReplyError(404)

        params = {}
        for key, value in kwargs.items():
            if hasattr(cls.model, key) and key != "pk":
                params[key] = value

        password = params.pop('password', None)
        if not password:
            raise ReplyError(400)

        password = password.encode('utf-8')
        password = bcrypt.hashpw(password, bcrypt.gensalt())
        params['password'] = password.decode('utf-8')

        # if this is the first user make it admin
        try:
            cls.model.objects.get()
        except:
            params['role'] = 'admin'

        try:
            user = cls.model.objects.create(**params)
        except NotUniqueError as e:
            raise ReplyError(409, cls.model.get_message_from_exception(e))
        except InvalidDocumentError as e:
            raise ReplyError(400, cls.model.get_message_from_exception(e))

        token = jwt.encode({
            'id': str(user.pk),
            'role': user.role,
        },
                           SECRET,
                           algorithm='HS256')
        request.send({'token': token.decode('utf8'), 'userId': str(user.pk)})
Пример #20
0
    def get(cls, request, pk=None, *args, **kwargs):
        if pk:
            try:
                response = Task.objects.get(id=pk)
            except DoesNotExist:
                raise ReplyError(404)

            response = response.get_dict()
        else:
            response = Task.objects.order_by('-date_added')[:cls.page_size]
            response = [r.get_dict() for r in response]

        request.send(response)
Пример #21
0
    def call(self, method, flow=None, job=None, *args, **kwargs):
        if method not in URLS:
            raise ReplyError(404)

        # select job from flow method
        job_name = None
        if flow:
            job_name = flow.job.job
        if job:
            job_name = job.job

        # select path
        verb, path = URLS.get(method)
        url = urljoin(self.api_url, path).format(job_name=job_name, **kwargs)

        response, headers = yield self.fetch(
            method=verb,
            url=url,
            follow_redirects=False,
        )

        raise gen.Return(response)
Пример #22
0
    def put(cls, request, pk, *args, **kwargs):
        """
            Updates a Task and executes the callback method.

        :param request: the client request
        :param pk: primary key
        :param args: not used
        :param kwargs: Body of the HTTP request
        :return: None
        """
        try:
            task = Task.objects.get(id=pk)
        except DoesNotExist:
            log.error('Task %s does not exist, but status was reported', pk)
            raise ReplyError(404)

        task.result = kwargs.get('result')
        task.console_output = kwargs.get('console_output')
        task.save()

        # call callback for this task
        callback_method = task.callback
        if not callback_method:
            request.send()
            return

        if task.status == FAILURE:
            log.info('Task %s finished with status %s',
                     task.pk, task.status)
            return

        yield callback_method(task=task, response=kwargs.get('result'))

        # send the updated Task object
        request.broadcast(
            task.get_dict(),
            verb='PUT', resource='/api/v1/tasks/{}'.format(pk)
        )
Пример #23
0
    def build_callback(cls, task, response):
        """
        Passed to the Task object, this function represents the callback
        that will be executed when the task finishes successfully.
        Creates the Build object and broadcasts the change to all
        connected clients.

        :param task: Task object that was passed this callback.
        :type task: raccoon.models.task.Task
        :param response: The result of the task. Not used.
        :return: None
        """
        project_id = task.context.get('project', {}).get('id')
        branch = task.context.get('branch')
        version = task.context.get('version')

        # get project
        try:
            project = Project.objects.get(id=project_id)
        except DoesNotExist:
            raise ReplyError(404)

        # Get commits and create changelog
        changelog = yield project.connector.interface.commits(project=project,
                                                              branch=branch)

        # Create the Build instance
        build = Build(project=project,
                      task=task,
                      branch=branch,
                      version=version,
                      changelog=changelog)
        build.save()

        # Notify clients about the new Build instance
        broadcast(build.get_dict(), verb='post', resource='/api/v1/builds/')
Пример #24
0
 def patch(cls, *args, **kwargs):
     raise ReplyError(501)
Пример #25
0
    def build(self, request, flow=None, job=None, *args, **kwargs):
        """
        Called by the Jenkins controller to perform a build job.
        Executes trigger with the modified context and build_callback
        for the task.

        :param request: client request
        :type request: raccoon.utils.request.Request
        :param flow: flow that triggered the build
        :type flow: raccoon.models.flow.Flow
        :param job: job information
        :type job: raccoon.models.job.Job
        :param args: arguments, passed to trigger
        :param kwargs: keyword args representing the context sent by the flow.
        :return: None
        """
        context = kwargs.copy()
        project_id = context.get('project', {}).get('id')
        version = context.get('version')
        branch = context.get('branch')

        # Get the project to build, and increment the build counter
        try:
            project = Project.objects.get(id=project_id)
        except DoesNotExist:
            raise ReplyError(404)

        project.version = version
        project.build_counter += 1
        project.save()

        # Create the new version and update the context with the correct values
        version += "-" + str(project.build_counter)

        context.update({
            'project': project.get_dict(),
            'version': version,
        })

        context.update(
            {'job_arguments': translate_job_arguments(job.arguments, context)})

        # Notify clients about project changes.
        request.broadcast(project.get_dict(),
                          verb='put',
                          resource='/api/v1/projects/')

        # Log build started
        user = request.user
        audit_log = AuditLog(user=user.email,
                             action='build',
                             project=project.name,
                             message='Build {} started for branch {}.'.format(
                                 version, branch))
        audit_log.save()

        request.broadcast(audit_log.get_dict(),
                          verb='post',
                          resource='/api/v1/auditlogs/',
                          admin_only=True)

        # Trigger the job in Jenkins
        yield self.trigger(request,
                           flow,
                           callback_method=self.build_callback,
                           *args,
                           **context)
Пример #26
0
 def put(cls, request, *args, **kwargs):
     raise ReplyError(501)
Пример #27
0
 def get(cls, request, *args, **kwargs):
     raise ReplyError(405)
Пример #28
0
 def get(cls, request, pk=None, *args, **kwargs):
     raise ReplyError(501)
Пример #29
0
 def delete(cls, *args, **kwargs):
     raise ReplyError(501)
Пример #30
0
 def delete(cls, request, *args, **kwargs):
     raise ReplyError(405)