Beispiel #1
0
class Bookmark(db.Model):
    """A bookmark is a "saved" link to an ActivityPub object that is not
    an actor."""
    __tablename__ = 'boookmarks'

    id = db.Column(db.Integer(), primary_key=True)
    description = db.Column(db.String())

    object_id = db.Column(
        db.Integer(),
        db.ForeignKey('objects.id',
                      ondelete='CASCADE',
                      name='fk_bookmark_object'))

    actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id',
                      ondelete='CASCADE',
                      name='fk_bookmark_actor'))

    group_id = db.Column(db.Integer(),
                         db.ForeignKey('bookmark_groups.id',
                                       ondelete='SET NULL',
                                       name='fk_bookmark_group'),
                         nullable=True)

    created = db.Column(db.DateTime())
Beispiel #2
0
class Object(db.Model):
    """Objects are the Things in the fediverse.

    From the ActivityPub specifications:
    https://www.w3.org/TR/activitypub/#obj
    """
    __tablename__ = 'objects'

    id = db.Column(db.Integer(), primary_key=True)
    uri = db.Column(db.String())
    actor_uri = db.Column(db.String())
    reply_to_uri = db.Column(db.String())
    object_type = db.Column(db.String())

    created = db.Column(db.DateTime())
    created_by_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id',
                      ondelete='SET NULL',
                      name='fk_object_created_by_actor'),
        nullable=True,
    )
    last_updated = db.Column(db.DateTime())

    data = db.Column(JSONB())
Beispiel #3
0
class Blog(db.Model):
    """An account can have more than one blog. Each blog connects an
    account to a single ActivityPub actor.
    """
    __tablename__ = 'blogs'

    id = db.Column(db.Integer(), primary_key=True)
    actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id', ondelete='CASCADE', name='fk_blog_actor'),
    )

    # Non-standard profile customizations
    page_background_color = db.Column(db.String())
    page_background_image = db.Column(db.String())
    page_background_tiled = db.Column(db.Boolean())
    page_background_static = db.Column(db.Boolean())
    section_background_color = db.Column(db.String())
    section_header_image = db.Column(db.String())
    section_text_color = db.Column(db.String())
    section_text_shadow = db.Column(db.Boolean())
    section_text_shadow_color = db.Column(db.Boolean())
    # Can be disabled
    disabled = db.Column(db.Boolean())
    # Can be soft deleted. To permanently delete, delete the associated account.
    # This is done to prevent abuse in the form of rapidly created temporary
    # accounts being used to torment and then removed after a block/mute.
    deleted = db.Column(db.Boolean())
    created = db.Column(db.DateTime())
    last_updated = db.Column(db.DateTime())
Beispiel #4
0
class Filter(db.Model):
    """Filters are best described as a more nuanced version of muting, allowing
    specific words to be removed from a timeline or minimized within the
    timeline.
    """
    __tablename__ = 'filters'

    id = db.Column(db.Integer(), primary_key=True)
    account_id = db.Column(
        db.Integer(),
        db.ForeignKey('accounts.id',
                      ondelete='CASCADE',
                      name='fk_filter_account'),
    )

    query = db.Column(db.String)
    hide = db.Column(db.Boolean)
    minimize = db.Column(db.Boolean)
    filter_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id',
                      ondelete='CASCADE',
                      name='fk_filter_applied_to_actor'),
    )

    created = db.Column(db.DateTime())
    duration = db.Column(db.Interval())
    forever = db.Column(db.Boolean())
Beispiel #5
0
class Import(db.Model):
    """Followed, muted, and blocked actors can be imported from ActivityPub
    files.

    These imports should not run automatically because of the potential
    for abuse, particularly from "follow" actions. Instead, they are stored here
    and can be selectively authorized to move forward by moderators.
    """
    __tablename__ = 'imports'

    id = db.Column(db.Integer(), primary_key=True)

    request_by_account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id', ondelete='CASCADE', name='fk_import_requested_by'),
    )
    request_for_identity_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'identities.id', ondelete='CASCADE',
            name='fk_import_for_identity'),
    )
    data_to_import = db.Column(JSONB())

    created = db.Column(db.DateTime())
    last_updated = db.Column(db.DateTime())
    processed = db.Column(db.DateTime())

    allowed = db.Column(db.Boolean())
Beispiel #6
0
class Notification(db.Model):
    """Our fancy notification class."""
    id = db.Column(db.Integer(), primary_key=True)

    category = db.Column(db.String())
    object_uri = db.Column(db.String())
    icon = db.Column(db.String())

    seen = db.Column(db.Boolean())
    acknowledged = db.Column(db.Boolean())

    created_by_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id',
                      ondelete='SET NULL',
                      name='fk_notification_by_actor'),
        nullable=True,
    )
    for_identity_id = db.Column(
        db.Integer(),
        db.ForeignKey('identities.id',
                      ondelete='CASCADE',
                      name='fk_notification_for_identity'),
    )
    created = db.Column(db.DateTime())
Beispiel #7
0
class Follow(db.Model):
    """Subscriptions are a link between actors. When you subscribe, you are
    saying, "yes, please, show me the things that you create."

    This activity is explicitly supported by ActivityPub, but I call it out
    explicitly for convenience. Explicitly..
    """
    __tablename__ = 'follows'

    id = db.Column(db.Integer(), primary_key=True)
    actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id', ondelete='CASCADE', name='fk_follow_actor'),
    )
    target_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id',
                      ondelete='CASCADE',
                      name='fk_follow_target_actor'),
    )

    # Waiting for account review
    pending_review = db.Column(db.Boolean())
    # Approved by actor owner
    approved = db.Column(db.Boolean())
    # Hard rejected by owner, all future requests will also be blocked
    blocked = db.Column(db.Boolean())

    created = db.Column(db.DateTime())
    last_updated = db.Column(db.DateTime())
Beispiel #8
0
class Actor(db.Model):
    """Actually, actors are objects (or ActivityStreams), but I call them out
    explicitly, because they are a key component in how lamia sees the
    fediverse.

    From the ActivityPub specifications:
    https://www.w3.org/TR/activitypub/#actors

    All the world’s a stage,
    And all the identities and blogs merely actors
    - Snakespeare, As You Lamia It
    """
    __tablename__ = 'actors'

    id = db.Column(db.Integer(), primary_key=True)
    actor_type = db.Column(db.String())
    private_key = db.Column(db.String(), nullable=True)

    display_name = db.Column(db.String())
    user_name = db.Column(db.String())
    uri = db.Column(db.String())
    local = db.Column(db.Boolean())

    created = db.Column(db.DateTime())
    last_updated = db.Column(db.DateTime())

    data = db.Column(JSONB())

    def generate_keys(self):
        """Create new keys and stuff them into an already created actor"""
        key = RSA.generate(2048)
        self.private_key = key.export_key("PEM").decode()

        try:
            del self.data['publicKey']
        except KeyError:
            pass

        self.data['publicKey'] = {
            'id': f'{BASE_URL}/u/{self.user_name}#main-key',
            'owner': f'{BASE_URL}/u/{self.user_name}',
            'publicKeyPem': key.publickey().export_key("PEM").decode()
        }

    # Convenience fields for local actors.
    identity_id = db.Column(db.Integer(),
                            db.ForeignKey('identities.id',
                                          ondelete='SET NULL',
                                          name='fk_actor_identity'),
                            nullable=True)
    blog_id = db.Column(db.Integer(),
                        db.ForeignKey('blogs.id',
                                      ondelete='SET NULL',
                                      name='fk_actor_blog'),
                        nullable=True)
Beispiel #9
0
class Account(db.Model):
    """Login details for user actors are stored here. Accounts in lamia are
    best explained as being containers for blogs and identities, where both of
    these are 1-to-1 connections to ActivityPub actors.

    Basically, an account on lamia can own multiple identities and blogs. These
    identities and blogs are what others see when someone with an account makes
    a post.

    TODO: to prevent intentional/unintentional abuse, this needs to be VERY
    transparent.
    """
    __tablename__ = 'accounts'

    id = db.Column(db.Integer(), primary_key=True)
    primary_identity_id = db.Column(
        db.Integer(),
        db.ForeignKey('identities.id',
                      ondelete='SET NULL',
                      name='fk_actor_primary_identity'),
        nullable=True,
    )
    email_address = db.Column(db.String())
    # Should be a hash encrypted by scrypt
    password = db.Column(db.String())
    created = db.Column(db.DateTime())
    # Activates low bandwidth mode to control image transmissions
    low_bandwidth = db.Column(db.Boolean())
    # Content should be hidden and attachments marked as sensitive by default
    sensitive_content = db.Column(db.Boolean())
    # All follows require confirmation if this is true
    approval_for_follows = db.Column(db.Boolean())

    # Either someone is banned or not banned.
    # Problematic users, nazis, etc should be banned without mercy or guilt.
    # Just f*****g do it, they aren't worth having around.
    # You shouldn't even have to think about it. Do it. Do it now.
    # Unless a deletion is desired. In which case, there is a way to do this.
    banned = db.Column(db.Boolean())
    # Profile customizations enabled/disabled for this account
    disable_profile_customizations = db.Column(db.Boolean())

    def set_password(self, password: str) -> None:
        """Hash a plaintext password and set the class property."""
        self.password = bcrypt.hashpw(password.encode(),
                                      bcrypt.gensalt()).decode()

    def check_password(self, password: str) -> bool:
        """Compare a plaintext password to the class property."""
        return bcrypt.checkpw(password.encode(), self.password.encode())
Beispiel #10
0
class FeedTag(db.Model):
    """A single tag watched by a feed."""
    __tablename__ = 'feed_tags'

    id = db.Column(db.Integer(), primary_key=True)
    feed_id = db.Column(
        db.Integer(),
        db.ForeignKey('feeds.id', ondelete='CASCADE', name='fk_feedtag_feed'),
    )
    target_tag_id = db.Column(
        db.Integer(),
        db.ForeignKey('tags.id', ondelete='CASCADE', name='fk_feedtag_tag'),
    )
    created = db.Column(db.DateTime())
Beispiel #11
0
class ObjectTag(db.Model):
    """A crosswalk table that ties tags to objects, for ease of querying."""
    __tablename__ = 'object_tags'

    id = db.Column(db.Integer(), primary_key=True)
    tag_id = db.Column(
        db.Integer(),
        db.ForeignKey('tags.id', ondelete='CASCADE', name='fk_objecttag_tag'),
    )
    object_id = db.Column(
        db.Integer(),
        db.ForeignKey('objects.id',
                      ondelete='CASCADE',
                      name='fk_objecttag_object'))
    created = db.Column(db.DateTime())
Beispiel #12
0
class Tag(db.Model):
    """The metaphysical concept of the hashtag, made real and without conceit."""
    __tablename__ = 'tags'

    id = db.Column(db.Integer(), primary_key=True)
    tag = db.Column(db.String())
    created = db.Column(db.DateTime())
Beispiel #13
0
class DomainEmailBlock(db.Model):
    """Email domain blocks prevent harmful registrations and registrations
    from spammers.
    """
    __tablename__ = 'domain_email_block'

    id = db.Column(db.Integer(), primary_key=True)
    domain = db.Column(db.String())
    created = db.Column(db.DateTime())
    created_by_account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id',
            ondelete='SET NULL',
            name='fk_domainemailblock_created_by_account'),
        nullable=True)
Beispiel #14
0
class FeedActor(db.Model):
    """A single actor watched by a feed."""
    __tablename__ = 'feed_actors'

    id = db.Column(db.Integer(), primary_key=True)
    feed_id = db.Column(
        db.Integer(),
        db.ForeignKey('feeds.id', ondelete='CASCADE',
                      name='fk_feedactor_feed'),
    )
    target_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id',
                      ondelete='CASCADE',
                      name='fk_feedactor_actor'))
    created = db.Column(db.DateTime())
Beispiel #15
0
class OauthApplication(db.Model):
    """An external application or service."""
    __tablename__ = 'oauth_applications'

    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String())
    website = db.Column(db.String())

    redirect_uri = db.Column(db.String())
    scopes = db.Column(db.String())
    client_id = db.Column(db.String())
    client_secret = db.Column(db.String())

    owner_id = db.Column(db.Integer())

    created = db.Column(db.DateTime())
    last_updated = db.Column(db.String())
Beispiel #16
0
class BookmarkGroup(db.Model):
    """Bookmark groups can be implicitly created to organize bookmarks"""
    __tablename__ = 'bookmark_groups'

    id = db.Column(db.Integer(), primary_key=True)
    group = db.Column(db.String())

    created = db.Column(db.DateTime())
Beispiel #17
0
class DomainCensor(db.Model):
    """A domain censor is a light server to server moderation action.

    An domain censor is a created at the server level, and forces all activities
    from a specific domain to either appear content warned or minimized.
    """
    __tablename__ = 'domain_censors'

    id = db.Column(db.Integer(), primary_key=True)
    domain = db.Column(db.String())
    created = db.Column(db.DateTime())
    created_by_account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id',
            ondelete='SET NULL',
            name='fk_domaincensor_created_by_account'),
        nullable=True)
Beispiel #18
0
class Feed(db.Model):
    """A feed is a set of actors that you want to create a custom timeline
    for. As an example, you could create a feed labeled "friends" for calling
    out your friends' blog posts and statuses.

    It's worth noting that you need to subscribe to an actor before this works.
    """
    __tablename__ = 'feeds'

    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String())
    identity_id = db.Column(
        db.Integer(),
        db.ForeignKey('identities.id',
                      ondelete='CASCADE',
                      name='fk_feed_identity'),
    )
    created = db.Column(db.DateTime())
Beispiel #19
0
class DomainBlock(db.Model):
    """A domain block is a severe server to client moderation action.

    A domain block is a created at the server level, and probits all actors on
    a blocked site from interacting with the local site. It also breaks
    follows between actors on this site and the site that is blocked.
    """
    __tablename__ = 'domain_blocks'

    id = db.Column(db.Integer(), primary_key=True)
    domain = db.Column(db.String())
    created = db.Column(db.DateTime())
    created_by_account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id',
            ondelete='SET NULL',
            name='fk_domainblock_created_by_account'),
        nullable=True)
Beispiel #20
0
class Interest(db.Model):
    """A hashtag but for actors. Maps tags to actors. Works similarly to
    the FeedTag model but has a much broader use-case. Basically, can be
    used to find users with similar interests.
    """
    __tablename__ = 'interests'
    id = db.Column(db.Integer(), primary_key=True)

    actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id',
                      ondelete='CASCADE',
                      name='fk_interest_map_actor'),
    )
    tag_id = db.Column(
        db.Integer(),
        db.ForeignKey('tags.id',
                      ondelete='CASCADE',
                      name='fk_interest_map_tag'),
    )
Beispiel #21
0
class Attachments(db.Model):
    """An attachment is an image tied to some kind of ActivityPub object.

    TODO: We can look into non-image attachments and probably dismiss the
    possibility of them later on.
    """
    __tablename__ = 'attachments'

    id = db.Column(db.Integer(), primary_key=True)
    uploaded_by_actor_uri = db.Column(db.String())
    alt_text = db.Column(db.String())

    storage_path = db.Column(db.String())
    storage_uri = db.Column(db.String())
    remote_uri = db.Column(db.String())

    size_in_bytes = db.Column(db.Integer())
    local = db.Column(db.Boolean())

    created = db.Column(db.DateTime())
Beispiel #22
0
class DomainMute(db.Model):
    """A domain mute is a moderate server to server moderation action.

    A domain mute is a created at the server level, and either hides the domains'
    activities from the federated timeline or minimizes them.
    """
    __tablename__ = 'domain_mutes'

    id = db.Column(db.Integer(), primary_key=True)
    domain = db.Column(db.String())
    created = db.Column(db.DateTime())
    duration = db.Column(db.Interval())
    forever = db.Column(db.Boolean())
    created_by_account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id',
            ondelete='SET NULL',
            name='fk_domainmute_created_by_account'),
        nullable=True)
Beispiel #23
0
class ReportComment(db.Model):
    """Allow moderator to moderator talk in a report, should work like chat
    messages."""
    __tablename__ = 'report_comments'

    message = db.Column(db.String())
    created_by_account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id',
            ondelete='SET NULL',
            name='fk_reportcomment_created_by_account'),
        nullable=True)
    created = db.Column(db.DateTime())

    report_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'reports.id', ondelete='CASCADE', name='fk_reportcomment_report'))
    # Changing the status of a report should create a comment where the message
    # is something like 'changed status from ignored to open'.
    status_change = db.Column(db.Boolean())
Beispiel #24
0
class OauthToken(db.Model):
    """A token created for our lovely API."""
    __tablename__ = 'oauth_tokens'

    id = db.Column(db.Integer(), primary_key=True)
    app_id = db.Column(
        db.Integer(), db.ForeignKey(
            'oauth_applications.id', ondelete='CASCADE'))
    account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id', ondelete='CASCADE', name='fk_oauthtoken_account'))

    access_token = db.Column(db.String())
    token_secret = db.Column(db.String())
    expires = db.Column(db.DateTime())
    created = db.Column(db.DateTime())

    def set_access_token(self, payload: dict, days: int = 7) -> str:
        """Encode a payload for this token."""
        now = pendulum.now()
        duration = pendulum.duration(days=days)
        expires = now + duration

        payload['created'] = now.to_iso8601_string()
        payload['expiration'] = expires.to_iso8601_string()

        self.expires = expires.naive()
        self.created = now.naive()

        secret = os.urandom(24).hex()
        self.token_secret = secret
        self.access_token = jwt.encode(
            payload, secret, algorithm='HS256').decode()
        return self.access_token

    def decode_access_token(self, token: str) -> dict:
        """Decode a previously encoded token."""
        return jwt.decode(token, self.token_secret, algorithms=['HS256'])
Beispiel #25
0
class Setting(db.Model):
    """A basic key and value storage for settings.

    Note: From a philosophical standpoint, settings should all be optional, and
    built around non-essential functionality. The installation of lamia should
    require as little gymnastics as possible and should be, dare I say, Fun.

    TODO: figure out settings we may need here.
    """
    __tablename__ = 'settings'

    id = db.Column(db.Integer(), primary_key=True)
    key = db.Column(db.String())
    value = db.Column(JSONB())
Beispiel #26
0
class Emoji(db.Model):
    """A mapping of images, replacement text, and description text all of which
    exists for the purpose of proliferating blob emojis and upside down smiley
    faces throughout the fediverse.
    """
    __tablename__ = 'emojis'

    id = db.Column(db.Integer(), primary_key=True)
    # A path to the image in /statics
    image = db.Column(db.String())
    replacement = db.Column(db.String())
    # Only needed if replacement doesn't do it justice
    description = db.Column(db.String())
    # for linear algebra with emojis, in this essay, i will...
    set_name = db.Column(db.String())
Beispiel #27
0
class ActorBlock(db.Model):
    """An actor block is a severe server to client moderation action.

    An actor block is a created at the server level, and probits the blocked
    actor from interacting with the site. It also breaks follows between
    actors on this site and the actor that is blocked.
    """
    __tablename__ = 'actor_blocks'

    id = db.Column(db.Integer(), primary_key=True)
    target_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'actors.id', ondelete='CASCADE',
            name='fk_actorblock_target_actor'),
    )
    created = db.Column(db.DateTime())
    created_by_account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id',
            ondelete='SET NULL',
            name='fk_actorblock_created_by_account'),
        nullable=True)
Beispiel #28
0
class Block(db.Model):
    """Blocking a user is the strongest user to user moderation action.

    It's a way of saying, "remove all of this user's stuff from my sight."

    Blocking a user prevents all interaction between the users, breaks follows,
    and more or less cleanses the timeline of that user.

    Blocks are officially a part of the ActivityPub specification, but I am
    calling them out explicitly with a seperate model for consistency.
    """
    __tablename__ = 'blocks'

    id = db.Column(db.Integer(), primary_key=True)
    account_id = db.Column(
        db.Integer(),
        db.ForeignKey('accounts.id',
                      ondelete='CASCADE',
                      name='fk_block_account'))
    target_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('accounts.id', ondelete='CASCADE',
                      name='fk_block_actor'))
    created = db.Column(db.DateTime())
Beispiel #29
0
class Mute(db.Model):
    """Muting a user is a lightweight user to user moderation action.

    It's a way of saying, "you're a bit much. i need some space." This can be a
    temporary mute, for example, just for a moment to allow someone else to
    cool down.

    Muting a user silences notifications, maintains follows, and either
    squelches them from timelines or minimizes them.
    """
    __tablename__ = 'mutes'

    id = db.Column(db.Integer(), primary_key=True)
    account_id = db.Column(
        db.Integer(),
        db.ForeignKey('accounts.id',
                      ondelete='CASCADE',
                      name='fk_mute_account'),
    )
    target_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey('actors.id', ondelete='CASCADE', name='fk_mute_actor'),
    )
    created = db.Column(db.DateTime())
Beispiel #30
0
class ActorMute(db.Model):
    """An actor block is a moderate server to client moderation action.

    An actor mute is a created at the server level, and either hides the actor's
    activities from the federated timeline or minimizes them.
    """
    __tablename__ = 'actor_mutes'

    id = db.Column(db.Integer(), primary_key=True)
    target_actor_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'actors.id', ondelete='CASCADE', name='fk_actormute_target_actor'),
    )
    created = db.Column(db.DateTime())
    duration = db.Column(db.Interval())
    forever = db.Column(db.Boolean())
    created_by_account_id = db.Column(
        db.Integer(),
        db.ForeignKey(
            'accounts.id',
            ondelete='SET NULL',
            name='fk_actormute_created_by_account'),
        nullable=True)