Example #1
0
class Permission(db.Model):
    __tablename__ = 'permission'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(255), nullable=False)
    resource_id = db.Column(db.Integer, db.ForeignKey('resource.id'))

    def __init__(self, name):
        self.name = name
Example #2
0
class ScheduledWorkflow(db.Model):
    """A SqlAlchemy table representing a workflow scheduled for execution

    Attributes:
        id (int): The primary key
        workflow_id (UUID): The id of the workflow scheduled for execution
    """
    __tablename__ = 'scheduled_workflow'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    workflow_id = db.Column(UUIDType(binary=False), nullable=False)
    task_id = db.Column(db.Integer, db.ForeignKey('scheduled_task.id'))
Example #3
0
class CaseSubscription(db.Model, TrackModificationsMixIn):
    """
    The ORM for the case subscriptions configuration
    """
    __tablename__ = 'case_subscription'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(255), nullable=False)
    subscriptions = db.Column(JSONType)
    note = db.Column(db.String)

    def __init__(self, name, subscriptions=None, note=''):
        """
        Constructs an instance of a CaseSubscription.
        
        Args:
            name (str): Name of the case subscription.
            subscriptions (list(dict)): A subscription JSON object.
            note (str, optional): Annotation of the event.
        """
        self.name = name
        self.note = note
        if subscriptions is None:
            subscriptions = []
        try:
            self.subscriptions = subscriptions
        except json.JSONDecodeError:
            self.subscriptions = '[]'

    def as_json(self):
        """ Gets the JSON representation of the CaseSubscription object.
        
        Returns:
            The JSON representation of the CaseSubscription object.
        """
        return {
            "id": self.id,
            "name": self.name,
            "subscriptions": self.subscriptions,
            "note": self.note
        }

    @staticmethod
    def from_json(name, subscription_json):
        """ Forms a CaseSubscription object from the provided JSON object.
        
        Args:
            name (str): The name of the case
            subscription_json (dict): A JSON representation of the subscription
            
        Returns:
            The CaseSubscription object parsed from the JSON object.
        """
        return CaseSubscription(name, subscriptions=subscription_json)
Example #4
0
class BlacklistedToken(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    jti = db.Column(db.String(36), nullable=False)
    user_identity = db.Column(db.String(50), nullable=False)
    expires = db.Column(db.DateTime, nullable=False)

    def as_json(self):
        """Get the JSON representation of a BlacklistedToken object.

        Returns:
            (dict): The JSON representation of a BlacklistedToken object.
        """
        return {
            'id': self.id,
            'jti': self.jti,
            'user': self.user_identity,
            'expires': str(self.expires)
        }
Example #5
0
class Resource(db.Model, TrackModificationsMixIn):
    __tablename__ = 'resource'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(255), nullable=False)
    role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
    permissions = db.relationship('Permission', backref=db.backref('resource'), cascade='all, delete-orphan')

    def __init__(self, name, permissions):
        """Initializes a new Resource object, which is a type of Resource that a Role may have access to.

        Args:
            name (str): Name of the Resource object.
            permissions (list[str]): List of permissions ("create", "read", "update", "delete", "execute")
                for the Resource
        """
        self.name = name
        self.set_permissions(permissions)

    def set_permissions(self, new_permissions):
        """Adds the given list of permissions to the Resource object.

        Args:
            new_permissions (list|set[str]): A list of permission names with which the Resource will be associated.
                These permissions must be in the set ["create", "read", "update", "delete", "execute"].
        """
        self.permissions = []
        new_permission_names = set(new_permissions)
        self.permissions.extend([Permission(permission) for permission in new_permission_names])

    def as_json(self, with_roles=False):
        """Returns the dictionary representation of the Resource object.

        Args:
            with_roles (bool, optional): Boolean to determine whether or not to include Role objects associated with the
                Resource in the JSON representation. Defaults to False.
        """
        out = {'id': self.id, 'name': self.name, 'permissions': [permission.name for permission in self.permissions]}
        if with_roles:
            out["role"] = self.role.name
        return out
Example #6
0
class MessageHistory(db.Model):
    """A Flask-SqlAlchemy table which contains entries related to the history of a message

    Attributes:
        id (int): The primary key
        action (MessageAction): The action taken
        timestamp (datetime): The timestamp of the action
        user_id (int): The ID of the user who took the action
        username (str): The username of the user who took the action

    Args:
        user (User): The user who took the action
        action (MessageAction): The action taken
    """
    __tablename__ = 'message_history'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    action = db.Column(db.Enum(MessageAction, name='message_action'))
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer)
    username = db.Column(db.String)
    message_id = db.Column(db.Integer, db.ForeignKey('message.id'))

    def __init__(self, user, action):
        self.action = action
        self.user_id = user.id
        self.username = user.username

    def as_json(self):
        """gets a JSON representation of the message history entry

        Returns:
            dict: The JSON representation of the message history entry
        """
        return {
            'action': self.action.name,
            'user_id': self.user_id,
            'username': self.username,
            'id': self.id,
            'timestamp': utc_as_rfc_datetime(self.timestamp)
        }
Example #7
0
class MessageHistory(db.Model):
    __tablename__ = 'message_history'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    action = db.Column(db.Enum(MessageAction))
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer)
    username = db.Column(db.String)
    message_id = db.Column(db.Integer, db.ForeignKey('message.id'))

    def __init__(self, user, action):
        self.action = action
        self.user_id = user.id
        self.username = user.username

    def as_json(self):
        return {
            'action': self.action.name,
            'user_id': self.user_id,
            'username': self.username,
            'id': self.id,
            'timestamp': utc_as_rfc_datetime(self.timestamp)
        }
Example #8
0
class User(db.Model, TrackModificationsMixIn):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    roles = db.relationship('Role',
                            secondary=user_roles_association,
                            backref=db.backref('users', lazy='dynamic'))
    username = db.Column(db.String(80), unique=True, nullable=False)
    _password = db.Column('password', db.String(255), nullable=False)
    active = db.Column(db.Boolean, default=True)
    last_login_at = db.Column(db.DateTime)
    current_login_at = db.Column(db.DateTime)
    last_login_ip = db.Column(db.String(45))
    current_login_ip = db.Column(db.String(45))
    login_count = db.Column(db.Integer, default=0)

    def __init__(self, name, password, roles=None):
        """Initializes a new User object

        Args:
            name (str): The username for the User.
            password (str): The password for the User.
            roles (list[int]): List of Role ids for the User. Defaults to None.
        """
        self.username = name
        self._password = pbkdf2_sha512.hash(password)
        self.roles = []
        if roles:
            self.set_roles(roles)

    @hybrid_property
    def password(self):
        """Returns the password for the user.
        """
        return self._password

    @password.setter
    def password(self, new_password):
        """Sets the password for a user, and encrypts it.

        Args:
            new_password (str): The new password for the User.
        """
        self._password = pbkdf2_sha512.hash(new_password)

    def verify_password(self, password_attempt):
        """Verifies that the input password matches with the stored password.

        Args:
            password_attempt(str): The input password.

        Returns:
            True if the passwords match, False if not.
        """
        return pbkdf2_sha512.verify(password_attempt, self._password)

    def set_roles(self, new_roles):
        """Sets the roles for a User.

        Args:
            new_roles (list[int]|set(int)): A list of Role IDs for the User.
        """

        new_role_ids = set(new_roles)
        new_roles = Role.query.filter(
            Role.id.in_(new_role_ids)).all() if new_role_ids else []

        self.roles[:] = new_roles

        roles_not_added = new_role_ids - {role.id for role in new_roles}
        if roles_not_added:
            logger.warning(
                'Cannot add roles {0} to user {1}. Roles do not exist'.format(
                    roles_not_added, self.id))

    def login(self, ip_address):
        """Tracks login information for the User upon logging in.

        Args:
            ip_address (str): The IP address from which the User logged in.
        """
        self.last_login_at = self.current_login_at
        self.current_login_at = datetime.utcnow()
        self.last_login_ip = self.current_login_ip
        self.current_login_ip = ip_address
        self.login_count += 1

    def logout(self):
        """Tracks login/logout information for the User upon logging out.
        """
        if self.login_count > 0:
            self.login_count -= 1
        else:
            logger.warning(
                'User {} logged out, but login count was already at 0'.format(
                    self.id))
        db.session.commit()

    def has_role(self, role):
        """Checks if a User has a Role associated with it.

        Args:
            role (int): The ID of the Role.

        Returns:
            True if the User has the Role, False otherwise.
        """
        return role in [role.id for role in self.roles]

    def as_json(self, with_user_history=False):
        """Returns the dictionary representation of a User object.

        Args:
            with_user_history (bool, optional): Boolean to determine whether or not to include user history in the JSON
                representation of the User. Defaults to False.

        Returns:
            The dictionary representation of a User object.
        """
        out = {
            "id": self.id,
            "username": self.username,
            "roles": [role.as_json() for role in self.roles],
            "active": self.active
        }
        if with_user_history:
            out.update({
                "last_login_at":
                utc_as_rfc_datetime(self.last_login_at),
                "current_login_at":
                utc_as_rfc_datetime(self.current_login_at),
                "last_login_ip":
                self.last_login_ip,
                "current_login_ip":
                self.current_login_ip,
                "login_count":
                self.login_count
            })
        return out
Example #9
0
import logging
from datetime import datetime

from passlib.hash import pbkdf2_sha512
from sqlalchemy.ext.hybrid import hybrid_property

from walkoff.extensions import db
from walkoff.helpers import utc_as_rfc_datetime
from walkoff.serverdb.mixins import TrackModificationsMixIn
from walkoff.serverdb.role import Role

logger = logging.getLogger(__name__)

user_roles_association = db.Table(
    'user_roles_association',
    db.Column('role_id', db.Integer, db.ForeignKey('role.id')),
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')))


class User(db.Model, TrackModificationsMixIn):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    roles = db.relationship('Role',
                            secondary=user_roles_association,
                            backref=db.backref('users', lazy='dynamic'))
    username = db.Column(db.String(80), unique=True, nullable=False)
    _password = db.Column('password', db.String(255), nullable=False)
    active = db.Column(db.Boolean, default=True)
    last_login_at = db.Column(db.DateTime)
    current_login_at = db.Column(db.DateTime)
    last_login_ip = db.Column(db.String(45))
Example #10
0
class Message(db.Model):
    __tablename__ = 'message'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    subject = db.Column(db.String())
    body = db.Column(db.String(), nullable=False)
    users = db.relationship('User',
                            secondary=user_messages_association,
                            backref=db.backref('messages', lazy='dynamic'))
    workflow_execution_id = db.Column(UUIDType(binary=False), nullable=False)
    requires_reauth = db.Column(db.Boolean, default=False)
    requires_response = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    history = db.relationship('MessageHistory', backref='message', lazy=True)

    def __init__(self,
                 subject,
                 body,
                 workflow_execution_id,
                 users,
                 requires_reauth=False,
                 requires_response=False):
        self.subject = subject
        self.body = body
        self.workflow_execution_id = workflow_execution_id
        self.users = users
        self.requires_reauth = requires_reauth
        self.requires_response = requires_response

    def record_user_action(self, user, action):
        if user in self.users:
            if ((action == MessageAction.unread
                 and not self.user_has_read(user)) or
                (action == MessageAction.respond and
                 (not self.requires_response or self.is_responded()[0]))):
                return
            elif action == MessageAction.delete:
                self.users.remove(user)
            self.history.append(MessageHistory(user, action))

    def user_has_read(self, user):
        user_history = [
            history_entry for history_entry in self.history
            if history_entry.user_id == user.id
        ]
        for history_entry in user_history[::-1]:
            if history_entry.action in (MessageAction.read,
                                        MessageAction.unread):
                if history_entry.action == MessageAction.unread:
                    return False
                if history_entry.action == MessageAction.read:
                    return True
        else:
            return False

    def user_last_read_at(self, user):
        user_history = [
            history_entry for history_entry in self.history
            if history_entry.user_id == user.id
        ]
        for history_entry in user_history[::-1]:
            if history_entry.action == MessageAction.read:
                return history_entry.timestamp
        else:
            return None

    def get_read_by(self):
        return {
            entry.username
            for entry in self.history if entry.action == MessageAction.read
        }

    def is_responded(self):
        if not self.requires_response:
            return False, None, None
        for history_entry in self.history[::-1]:
            if history_entry.action == MessageAction.respond:
                return True, history_entry.timestamp, history_entry.username
        else:
            return False, None, None

    def as_json(self, with_read_by=True, user=None, summary=False):
        responded, responded_at, responded_by = self.is_responded()
        ret = {
            'id': self.id,
            'subject': self.subject,
            'created_at': utc_as_rfc_datetime(self.created_at),
            'awaiting_response': self.requires_response and not responded
        }

        if user:
            ret['is_read'] = self.user_has_read(user)
            last_read_at = self.user_last_read_at(user)
            if last_read_at:
                ret['last_read_at'] = utc_as_rfc_datetime(last_read_at)
        if not summary:
            ret.update({
                'body': json.loads(self.body),
                'workflow_execution_id': str(self.workflow_execution_id),
                'requires_reauthorization': self.requires_reauth,
                'requires_response': self.requires_response
            })
            if responded:
                ret['responded_at'] = utc_as_rfc_datetime(responded_at)
                ret['responded_by'] = responded_by
            if with_read_by:
                ret['read_by'] = list(self.get_read_by())
        return ret
Example #11
0
import json
from datetime import datetime

from sqlalchemy_utils import UUIDType

from walkoff.extensions import db
from walkoff.helpers import utc_as_rfc_datetime
from walkoff.messaging import MessageAction

user_messages_association = db.Table(
    'user_messages', db.Column('user_id', db.Integer,
                               db.ForeignKey('user.id')),
    db.Column('message_id', db.Integer, db.ForeignKey('message.id')))


class Message(db.Model):
    __tablename__ = 'message'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    subject = db.Column(db.String())
    body = db.Column(db.String(), nullable=False)
    users = db.relationship('User',
                            secondary=user_messages_association,
                            backref=db.backref('messages', lazy='dynamic'))
    workflow_execution_id = db.Column(UUIDType(binary=False), nullable=False)
    requires_reauth = db.Column(db.Boolean, default=False)
    requires_response = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    history = db.relationship('MessageHistory', backref='message', lazy=True)

    def __init__(self,
Example #12
0
class ScheduledWorkflow(db.Model):
    __tablename__ = 'scheduled_workflow'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    workflow_id = db.Column(UUIDType(), nullable=False)
    task_id = db.Column(db.Integer, db.ForeignKey('scheduled_task.id'))
Example #13
0
class Message(db.Model):
    """Flask-SqlAlchemy Table which holds messages for users.

    It has a many to many relationship with both the users and roles tables.

    Attributes:
        id (int): The primary key
        subject (str): The subject of the message
        body (str): The body of the message as a JSON string
        users (list[User]): The users to which this message was sent and who haven't deleted the message
        roles (list[Role]): The roles to which this message was sent
        workflow_execution_id (UUID): The execution id of the workflow which sent the message
        requires_reauth (bool): Does the message require reauthentication to address it?
        requires_response (bool): Does the message require a response?
        created_at (datetime): Timestamp of message creation
        history (list[MessageHistory]): The timeline of actions taken on this message

    Args:
        subject (str): The subject of the message
        body (str): The body of the message as a JSON string
        users (list[User]): The users to which this message was sent and who haven't deleted the message
        roles (list[Role]): The roles to which this message was sent
        requires_reauth (bool): Does the message require reauthentication to address it?
        requires_response (bool): Does the message require a response?
    """
    __tablename__ = 'message'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    subject = db.Column(db.String())
    body = db.Column(db.String(), nullable=False)
    users = db.relationship('User', secondary=user_messages_association, backref=db.backref('messages', lazy='dynamic'))
    roles = db.relationship('Role', secondary=role_messages_association, backref=db.backref('messages', lazy='dynamic'))
    workflow_execution_id = db.Column(UUIDType(binary=False), nullable=False)
    requires_reauth = db.Column(db.Boolean, default=False)
    requires_response = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    history = db.relationship('MessageHistory', backref='message', lazy=True)

    def __init__(self, subject, body, workflow_execution_id, users=None, roles=None, requires_reauth=False,
                 requires_response=False):
        self.subject = subject
        self.body = body
        self.workflow_execution_id = workflow_execution_id
        if not (users or roles):
            message = 'Message must have users and/or roles, but has neither.'
            logger.error(message)
            raise ValueError(message)
        self.users = users if users else []
        self.roles = roles if roles else []
        self.requires_reauth = requires_reauth
        self.requires_response = requires_response

    def record_user_action(self, user, action):
        """Records an action taken by a user on this message

        Args:
            user (User): The user taking the action
            action (MessageAction): The action taken
        """
        if user in self.users:
            if ((action == MessageAction.unread and not self.user_has_read(user))
                    or (action == MessageAction.respond and (not self.requires_response or self.is_responded()[0]))):
                return
            elif action == MessageAction.delete:
                self.users.remove(user)
            self.history.append(MessageHistory(user, action))

    def user_has_read(self, user):
        """Determines if a user has read the message

        Args:
            user (User): The user of the query

        Returns:
            (bool): Has the user read the message?
        """
        user_history = [history_entry for history_entry in self.history if history_entry.user_id == user.id]
        for history_entry in user_history[::-1]:
            if history_entry.action in (MessageAction.read, MessageAction.unread):
                if history_entry.action == MessageAction.unread:
                    return False
                if history_entry.action == MessageAction.read:
                    return True
        else:
            return False

    def user_last_read_at(self, user):
        """Gets the last time the user has read the message

        Args:
            user (User): The user of the query

        Returns:
            (datetime|None): The timestamp of the last time the user has read the message or None if the message has not
                been read by this user
        """
        user_history = [history_entry for history_entry in self.history if history_entry.user_id == user.id]
        for history_entry in user_history[::-1]:
            if history_entry.action == MessageAction.read:
                return history_entry.timestamp
        else:
            return None

    def get_read_by(self):
        """Gets all the usernames of the users who have read this message

        Returns:
            set(str): The usernames of the users who have read this message
        """
        return {entry.username for entry in self.history if entry.action == MessageAction.read}

    def is_responded(self):
        """Has this message been responded to?

        Returns:
            tuple(bool, datetime|None, str|None): A tuple of if the message has been responded to, if it has then the
                datetime of when it was responded to and username of the user who responded to it.
        """
        if not self.requires_response:
            return False, None, None
        for history_entry in self.history[::-1]:
            if history_entry.action == MessageAction.respond:
                return True, history_entry.timestamp, history_entry.username
        else:
            return False, None, None

    def is_authorized(self, user_id=None, role_ids=None):
        """Is a user authorized to respond to this message?

        Args:
            user_id (int): The ID of the user
            role_ids (list[int]): The ids of the roles the user has

        Returns:
            (bool)
        """
        if user_id:
            for user in self.users:
                if user_id == user.id:
                    return True

        if role_ids:
            if isinstance(role_ids, int):
                role_ids = [role_ids]
            for role_id in role_ids:
                for role in self.roles:
                    if role_id == role.id:
                        return True

        return False

    def as_json(self, with_read_by=True, user=None, summary=False):
        """Gets a JSON representation of the message

        Args:
            with_read_by (bool, optional): Should the JSON include who has read the message? Defaults to True.
            user (User, optional): If provided, information specific to the user is included.
            summary (bool, optional): If True, only give a brief summary of the messsage. Defaults to False.

        Returns:

        """
        responded, responded_at, responded_by = self.is_responded()
        ret = {'id': self.id,
               'subject': self.subject,
               'created_at': utc_as_rfc_datetime(self.created_at),
               'awaiting_response': self.requires_response and not responded}

        if user:
            ret['is_read'] = self.user_has_read(user)
            last_read_at = self.user_last_read_at(user)
            if last_read_at:
                ret['last_read_at'] = utc_as_rfc_datetime(last_read_at)
        if not summary:
            ret.update({'body': json.loads(self.body),
                        'workflow_execution_id': str(self.workflow_execution_id),
                        'requires_reauthorization': self.requires_reauth,
                        'requires_response': self.requires_response})
            if responded:
                ret['responded_at'] = utc_as_rfc_datetime(responded_at)
                ret['responded_by'] = responded_by
            if with_read_by:
                ret['read_by'] = list(self.get_read_by())
        return ret
class CaseSubscription(db.Model, TrackModificationsMixIn):
    """
    The ORM for the case subscriptions configuration
    """
    __tablename__ = 'case_subscription'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(255), nullable=False)
    subscriptions = db.Column(JSONType)
    note = db.Column(db.String)

    def __init__(self, name, subscriptions=None, note=''):
        """
        Constructs an instance of a CaseSubscription.
        
        Args:
            name (str): Name of the case subscription.
            subscriptions (list(dict)): A subscription JSON object.
            note (str, optional): Annotation of the event.
        """
        self.name = name
        self.note = note
        if subscriptions is None:
            subscriptions = []
        try:
            self.subscriptions = subscriptions
        except json.JSONDecodeError:
            self.subscriptions = '[]'
        finally:
            subscriptions = {
                subscription['id']: subscription['events']
                for subscription in subscriptions
            }
            walkoff.case.subscription.add_cases({name: subscriptions})

    def as_json(self):
        """ Gets the JSON representation of the CaseSubscription object.
        
        Returns:
            The JSON representation of the CaseSubscription object.
        """
        return {
            "id": self.id,
            "name": self.name,
            "subscriptions": self.subscriptions,
            "note": self.note
        }

    @staticmethod
    def update(case_name):
        """ Synchronizes the subscription from the subscription in memory in the core.
        
        Args:
            case_name (str): The name of case to synchronize.
        """
        case = CaseSubscription.query.filter_by(name=case_name).first()
        if case and case_name in walkoff.case.subscription.subscriptions:
            case_subs = walkoff.case.subscription.subscriptions[case_name]
            case.subscriptions = [{
                'id': uid,
                'events': events
            } for uid, events in case_subs.items()]
Example #15
0
class ScheduledTask(db.Model, TrackModificationsMixIn):
    __tablename__ = 'scheduled_task'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(255), nullable=False)
    description = db.Column(db.String(255), nullable=False)
    status = db.Column(db.Enum('running', 'stopped'))
    workflows = db.relationship('ScheduledWorkflow',
                                cascade="all, delete-orphan",
                                backref='post',
                                lazy='dynamic')
    trigger_type = db.Column(db.Enum('date', 'interval', 'cron',
                                     'unspecified'))
    trigger_args = db.Column(db.String(255))

    def __init__(self,
                 name,
                 description='',
                 status='running',
                 workflows=None,
                 task_trigger=None):
        self.name = name
        self.description = description
        if workflows is not None:
            for workflow in set(workflows):
                self.workflows.append(ScheduledWorkflow(workflow_id=workflow))
        if task_trigger is not None:
            construct_trigger(
                task_trigger)  # Throws an error if the args are invalid
            self.trigger_type = task_trigger['type']
            self.trigger_args = json.dumps(task_trigger['args'])
        else:
            self.trigger_type = 'unspecified'
            self.trigger_args = '{}'
        self.status = status if status in ('running', 'stopped') else 'running'
        if self.status == 'running' and self.trigger_type != 'unspecified':
            self._start_workflows()

    def update(self, json_in):
        trigger = None
        if 'task_trigger' in json_in and json_in['task_trigger']:
            trigger = construct_trigger(
                json_in['task_trigger']
            )  # Throws an error if the args are invalid
            self._update_scheduler(trigger)
            self.trigger_type = json_in['task_trigger']['type']
            self.trigger_args = json.dumps(json_in['task_trigger']['args'])
        if 'name' in json_in:
            self.name = json_in['name']
        if 'description' in json_in:
            self.description = json_in['description']
        if 'workflows' in json_in and json_in['workflows']:
            self._modify_workflows(json_in, trigger=trigger)
        if 'status' in json_in and json_in['status'] != self.status:
            self._update_status(json_in)

    def start(self):
        if self.status != 'running':
            self.status = 'running'
            if self.trigger_type != 'unspecified':
                self._start_workflows()

    def stop(self):
        if self.status != 'stopped':
            self.status = 'stopped'
            self._stop_workflows()

    def _update_status(self, json_in):
        self.status = json_in['status']
        if self.status == 'running':
            self._start_workflows()
        elif self.status == 'stopped':
            self._stop_workflows()

    def _start_workflows(self, trigger=None):
        from walkoff.server.flaskserver import running_context
        trigger = trigger if trigger is not None else construct_trigger(
            self._reconstruct_scheduler_args())
        running_context.scheduler.schedule_workflows(
            self.id, running_context.executor.execute_workflow,
            self._get_workflow_ids_as_list(), trigger)

    def _stop_workflows(self):
        from walkoff.server.flaskserver import running_context
        running_context.scheduler.unschedule_workflows(
            self.id, self._get_workflow_ids_as_list())

    def _modify_workflows(self, json_in, trigger):
        from walkoff.server.flaskserver import running_context

        new, removed = self.__get_different_workflows(json_in)
        for workflow in self.workflows:
            self.workflows.remove(workflow)
        for workflow in json_in['workflows']:
            self.workflows.append(ScheduledWorkflow(workflow_id=workflow))
        if self.trigger_type != 'unspecified' and self.status == 'running':
            trigger = trigger if trigger is not None else construct_trigger(
                self._reconstruct_scheduler_args())
            if new:
                running_context.scheduler.schedule_workflows(
                    self.id, running_context.executor.execute_workflow, new,
                    trigger)
            if removed:
                running_context.scheduler.unschedule_workflows(
                    self.id, removed)

    def _update_scheduler(self, trigger):
        from walkoff.server.flaskserver import running_context
        running_context.scheduler.update_workflows(self.id, trigger)

    def _reconstruct_scheduler_args(self):
        return {
            'type': self.trigger_type,
            'args': json.loads(self.trigger_args)
        }

    def _get_workflow_ids_as_list(self):
        return [workflow.workflow_id for workflow in self.workflows]

    def __get_different_workflows(self, json_in):
        original_workflows = set(self._get_workflow_ids_as_list())
        incoming_workflows = set(json_in['workflows'])
        new = incoming_workflows - original_workflows
        removed = original_workflows - incoming_workflows
        return new, removed

    def as_json(self):
        return {
            'id': self.id,
            'name': self.name,
            'description': self.description,
            'status': self.status,
            'workflows': self._get_workflow_ids_as_list(),
            'task_trigger': self._reconstruct_scheduler_args()
        }
Example #16
0
class ScheduledTask(db.Model, TrackModificationsMixIn):
    """A SqlAlchemy table representing a a task scheduled for periodic execution

    Attributes:
        id (int): The primary key
        name (str): The name of the task
        description (str): A description of the task
        status (str): The status of the task. either "running" or "stopped"
        workflows (list[ScheduledWorkflow]): The workflows attached to this task
        trigger_type (str): The type of trigger to use for the scheduler. Either "date", "interval", "cron", or
            "unspecified"
        trigger_args (str): The arguments for the scheduler trigger

    Args:
        name (str): The name of the task
        description (str, optional): A description of the task. Defaults to empty string
        workflows (list[str], optional): The uuids of the workflows attached to this task. Defaults to empty list,
        task_trigger (dict): A dict containing two fields: "type", which contains the type of trigger to use for the
            scheduler ("date", "interval", "cron", or "unspecified"), and "args", which contains the arguments for the
            scheduler trigger
    """
    __tablename__ = 'scheduled_task'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(255), nullable=False)
    description = db.Column(db.String(255), nullable=False)
    status = db.Column(db.Enum('running', 'stopped'))
    workflows = db.relationship('ScheduledWorkflow',
                                cascade="all, delete-orphan",
                                backref='post',
                                lazy='dynamic')
    trigger_type = db.Column(db.Enum('date', 'interval', 'cron', 'unspecified'))
    trigger_args = db.Column(db.String(255))

    def __init__(self, name, description='', status='running', workflows=None, task_trigger=None):
        self.name = name
        self.description = description
        if workflows is not None:
            for workflow in set(workflows):
                self.workflows.append(ScheduledWorkflow(workflow_id=workflow))
        if task_trigger is not None:
            construct_trigger(task_trigger)  # Throws an error if the args are invalid
            self.trigger_type = task_trigger['type']
            self.trigger_args = json.dumps(task_trigger['args'])
        else:
            self.trigger_type = 'unspecified'
            self.trigger_args = '{}'
        self.status = status if status in ('running', 'stopped') else 'running'
        if self.status == 'running' and self.trigger_type != 'unspecified':
            self._start_workflows()

    def update(self, json_in):
        """Updates this task from a JSON representation of it

        Args:
            json_in (dict): The JSON representation of the updated task
        """
        trigger = None
        if 'task_trigger' in json_in and json_in['task_trigger']:
            trigger = construct_trigger(json_in['task_trigger'])  # Throws an error if the args are invalid
            self._update_scheduler(trigger)
            self.trigger_type = json_in['task_trigger']['type']
            self.trigger_args = json.dumps(json_in['task_trigger']['args'])
        if 'name' in json_in:
            self.name = json_in['name']
        if 'description' in json_in:
            self.description = json_in['description']
        if 'workflows' in json_in and json_in['workflows']:
            self._modify_workflows(json_in, trigger=trigger)
        if 'status' in json_in and json_in['status'] != self.status:
            self._update_status(json_in)

    def start(self):
        """Start executing this task
        """
        if self.status != 'running':
            self.status = 'running'
            if self.trigger_type != 'unspecified':
                self._start_workflows()

    def stop(self):
        """Stop executing this scheduled task
        """
        if self.status != 'stopped':
            self.status = 'stopped'
            self._stop_workflows()

    def _update_status(self, json_in):
        self.status = json_in['status']
        if self.status == 'running':
            self._start_workflows()
        elif self.status == 'stopped':
            self._stop_workflows()

    def _start_workflows(self, trigger=None):
        from flask import current_app
        trigger = trigger if trigger is not None else construct_trigger(self._reconstruct_scheduler_args())
        current_app.running_context.scheduler.schedule_workflows(self.id,
                                                                 current_app.running_context.executor.execute_workflow,
                                                                 self._get_workflow_ids_as_list(), trigger)

    def _stop_workflows(self):
        from flask import current_app
        current_app.running_context.scheduler.unschedule_workflows(self.id, self._get_workflow_ids_as_list())

    def _modify_workflows(self, json_in, trigger):
        from flask import current_app

        new, removed = self.__get_different_workflows(json_in)
        for workflow in self.workflows:
            self.workflows.remove(workflow)
        for workflow in json_in['workflows']:
            self.workflows.append(ScheduledWorkflow(workflow_id=workflow))
        if self.trigger_type != 'unspecified' and self.status == 'running':
            trigger = trigger if trigger is not None else construct_trigger(self._reconstruct_scheduler_args())
            if new:
                current_app.running_context.scheduler.schedule_workflows(self.id,
                                                                         current_app.running_context.executor.execute_workflow,
                                                                         new, trigger)
            if removed:
                current_app.running_context.scheduler.unschedule_workflows(self.id, removed)

    def _update_scheduler(self, trigger):
        from flask import current_app
        current_app.running_context.scheduler.update_workflows(self.id, trigger)

    def _reconstruct_scheduler_args(self):
        return {'type': self.trigger_type, 'args': json.loads(self.trigger_args)}

    def _get_workflow_ids_as_list(self):
        return [workflow.workflow_id for workflow in self.workflows]

    def __get_different_workflows(self, json_in):
        original_workflows = set(self._get_workflow_ids_as_list())
        incoming_workflows = set(json_in['workflows'])
        new = incoming_workflows - original_workflows
        removed = original_workflows - incoming_workflows
        return new, removed

    def as_json(self):
        """Gets a JSON representation of this ScheduledTask

        Returns:
            (dict): The JSON representation of this ScheduledTask
        """
        return {'id': self.id,
                'name': self.name,
                'description': self.description,
                'status': self.status,
                'workflows': self._get_workflow_ids_as_list(),
                'task_trigger': self._reconstruct_scheduler_args()}
Example #17
0
class TrackModificationsMixIn(object):
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
    modified_at = db.Column(db.DateTime,
                            default=db.func.current_timestamp(),
                            onupdate=db.func.current_timestamp())
Example #18
0
class Role(db.Model, TrackModificationsMixIn):
    __tablename__ = 'role'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    description = db.Column(db.String(255))
    resources = db.relationship('Resource',
                                backref=db.backref('role'),
                                cascade='all, delete-orphan')

    def __init__(self, name, description='', resources=None):
        """Initializes a Role object. Each user has one or more Roles associated with it, which determines the user's
            permissions.

        Args:
            name (str): The name of the Role.
            description (str, optional): A description of the role.
            resources (list(dict[name:resource, permissions:list[permission])): A list of dictionaries containing the
                name of the resource, and a list of permission names associated with the resource. Defaults to None.
        """
        self.name = name
        self.description = description
        self.resources = []
        if resources:
            self.set_resources(resources)

    def set_resources(self, new_resources):
        """Adds the given list of resources to the Role object.

        Args:
            new_resources (list(dict[name:resource, permissions:list[permission])): A list of dictionaries containing
                the name of the resource, and a list of permission names associated with the resource.
        """
        new_resource_names = set(
            [resource['name'] for resource in new_resources])
        current_resource_names = set(
            [resource.name
             for resource in self.resources] if self.resources else [])
        resource_names_to_add = new_resource_names - current_resource_names
        resource_names_to_delete = current_resource_names - new_resource_names
        resource_names_intersect = current_resource_names.intersection(
            new_resource_names)

        self.resources[:] = [
            resource for resource in self.resources
            if resource.name not in resource_names_to_delete
        ]

        for resource_perms in new_resources:
            if resource_perms['name'] in resource_names_to_add:
                self.resources.append(
                    Resource(resource_perms['name'],
                             resource_perms['permissions']))
            elif resource_perms['name'] in resource_names_intersect:
                resource = Resource.query.filter_by(
                    role_id=self.id, name=resource_perms['name']).first()
                if resource:
                    resource.set_permissions(resource_perms['permissions'])
        db.session.commit()

    def as_json(self, with_users=False):
        """Returns the dictionary representation of the Role object.

        Args:
            with_users (bool, optional): Boolean to determine whether or not to include User objects associated with the
                Role in the JSON representation. Defaults to False.

        Returns:
            (dict): The dictionary representation of the Role object.
        """
        out = {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "resources": [resource.as_json() for resource in self.resources]
        }
        if with_users:
            out['users'] = [user.username for user in self.users]
        return out