Exemplo n.º 1
0
def delete_group_access_logic(cls, user_or_token):
    """User can delete a group that is not the sitewide public group, is not
    a single user group, and that they are an admin member of."""
    user_id = UserAccessControl.user_id_from_user_or_token(user_or_token)
    query = (DBSession().query(cls).join(GroupUser).filter(
        cls.name != cfg['misc']['public_group_name']).filter(
            cls.single_user_group.is_(False)))
    if not user_or_token.is_system_admin:
        query = query.filter(GroupUser.user_id == user_id,
                             GroupUser.admin.is_(True))
    return query
Exemplo n.º 2
0
def shiftuser_delete_access_logic(cls, user_or_token):
    aliased = safe_aliased(cls)
    user_id = UserAccessControl.user_id_from_user_or_token(user_or_token)
    user_shift_admin = (
        DBSession()
        .query(Shift)
        .join(GroupUser, GroupUser.group_id == Shift.group_id)
        .filter(sa.and_(GroupUser.user_id == user_id, GroupUser.admin.is_(True)))
    )
    query = DBSession().query(cls).join(aliased, cls.shift_id == aliased.shift_id)
    if not user_or_token.is_system_admin:
        query = query.filter(
            sa.or_(
                aliased.user_id == user_id,
                sa.and_(aliased.admin.is_(True), aliased.user_id == user_id),
                aliased.shift_id.in_([shift.id for shift in user_shift_admin.all()]),
                aliased.needs_replacement.is_(True),
            )
        )
    return query
Exemplo n.º 3
0
def user_update_delete_logic(cls, user_or_token):
    """A user can update or delete themselves, and a super admin can delete
    or update any user."""

    if user_or_token.is_admin:
        return public.query_accessible_rows(cls, user_or_token)

    # non admin users can only update or delete themselves
    user_id = UserAccessControl.user_id_from_user_or_token(user_or_token)

    return DBSession().query(cls).filter(cls.id == user_id)
Exemplo n.º 4
0
def manage_shift_access_logic(cls, user_or_token):
    # admins of the shift and admins of the group associated with the shift can delete and update a shift
    user_id = UserAccessControl.user_id_from_user_or_token(user_or_token)
    query = DBSession().query(cls).join(GroupUser, cls.group_id == GroupUser.group_id)
    if not user_or_token.is_system_admin:
        admin_query = query.filter(
            GroupUser.user_id == user_id, GroupUser.admin.is_(True)
        )
        if admin_query.count() == 0:
            query = query.join(ShiftUser)
            query = query.filter(
                ShiftUser.user_id == user_id, ShiftUser.admin.is_(True)
            )
        else:
            query = admin_query
    return query
Exemplo n.º 5
0
def taxonomy_update_delete_logic(cls, user_or_token):
    """This function generates the query for taxonomies that the current user
    can update or delete. If the querying user doesn't have System admin or
    Delete taxonomy acl, then no taxonomies are accessible to that user under
    this policy . Otherwise, the only taxonomies that the user can delete are
    those that have no associated classifications, preventing classifications
    from getting deleted in a cascade when their parent taxonomy is deleted.
    """
    from .classification import Classification

    if len({'Delete taxonomy', 'System admin'}
           & set(user_or_token.permissions)) == 0:
        # nothing accessible
        return restricted.query_accessible_rows(cls, user_or_token)

    # dont allow deletion of any taxonomies that have classifications attached
    return (DBSession().query(cls).outerjoin(Classification).group_by(
        cls.id).having(sa.func.bool_and(Classification.id.is_(None))))
Exemplo n.º 6
0
def insert_into_phot_stat(mapper, connection, target):

    # Create or update PhotStat object
    @event.listens_for(DBSession(), "before_flush", once=True)
    def receive_after_flush(session, context, instances):
        obj_id = target.obj_id
        phot_stat = session.scalars(
            sa.select(PhotStat).where(PhotStat.obj_id == obj_id)).first()
        if phot_stat is None:
            all_phot = session.scalars(
                sa.select(Photometry).where(
                    Photometry.obj_id == obj_id)).all()
            phot_stat = PhotStat(obj_id=obj_id)
            phot_stat.full_update(all_phot)
            session.add(phot_stat)

        else:
            phot_stat.add_photometry_point(target)
            session.add(phot_stat)
Exemplo n.º 7
0
def updatable_by_token_with_listener_acl(cls, user_or_token):
    if user_or_token.is_admin:
        return public.query_accessible_rows(cls, user_or_token)

    instruments_with_apis = (
        Instrument.query_records_accessible_by(user_or_token).filter(
            Instrument.listener_classname.isnot(None)).all())

    api_map = {
        instrument.id: instrument.listener_class.get_acl_id()
        for instrument in instruments_with_apis
    }

    accessible_instrument_ids = [
        instrument_id for instrument_id, acl_id in api_map.items()
        if acl_id in user_or_token.permissions
    ]

    return (DBSession().query(cls).join(Allocation).join(Instrument).filter(
        Instrument.id.in_(accessible_instrument_ids)))
Exemplo n.º 8
0
    def area(self, cumulative_probability=1.0):
        """Integrated area in sq. deg within localization."""

        cum_prob = (sa.func.sum(
            LocalizationTile.probdensity * LocalizationTile.healpix.area).over(
                order_by=LocalizationTile.probdensity.desc()).label('cum_prob')
                    )
        localizationtile_subquery = (sa.select(
            LocalizationTile.probdensity, cum_prob).where(
                LocalizationTile.localization_id ==
                self.observation_plan_request.localization_id, ).distinct()
                                     ).subquery()

        min_probdensity = (sa.select(
            sa.func.min(localizationtile_subquery.columns.probdensity)).where(
                localizationtile_subquery.columns.cum_prob <=
                cumulative_probability)).scalar_subquery()

        tiles_subquery = (sa.select(InstrumentFieldTile.id).where(
            LocalizationTile.localization_id ==
            self.observation_plan_request.localization_id,
            LocalizationTile.probdensity >= min_probdensity,
            InstrumentFieldTile.instrument_id == self.instrument_id,
            InstrumentFieldTile.instrument_field_id == InstrumentField.id,
            InstrumentFieldTile.instrument_field_id ==
            PlannedObservation.field_id,
            PlannedObservation.observation_plan_id == self.id,
            InstrumentFieldTile.healpix.overlaps(LocalizationTile.healpix),
        ).distinct().subquery())

        union = sa.select(
            ha.func.union(InstrumentFieldTile.healpix).label('healpix'))
        union = union.join(tiles_subquery,
                           tiles_subquery.c.id == InstrumentFieldTile.id)
        area = sa.func.sum(union.columns.healpix.area)
        query_area = sa.select(area)
        intarea = DBSession().execute(query_area).scalar_one()

        if intarea is None:
            intarea = 0.0
        return intarea * (180.0 / np.pi)**2
Exemplo n.º 9
0
def group_create_logic(cls, user_or_token):
    """
    Can only add a user to a group if they have all the requisite
    streams required for entry to the group. And users cannot
    be added to single user groups through the Groups API (only
    through event handlers).
    """
    from .stream import Stream, StreamUser

    return (DBSession().query(cls).join(Group).outerjoin(
        Stream, Group.streams).outerjoin(
            StreamUser,
            sa.and_(
                StreamUser.user_id == cls.user_id,
                StreamUser.stream_id == Stream.id,
            ),
        ).filter(Group.single_user_group.is_(False)).group_by(cls.id).having(
            sa.or_(
                sa.func.bool_and(StreamUser.stream_id.isnot(None)),
                sa.func.bool_and(Stream.id.is_(None)),  # group has no streams
            )))
Exemplo n.º 10
0
def stream_delete_logic(cls, user_or_token):
    """Can only delete a stream from a user if none of the user's groups
    require that stream for membership.
    """
    from .group_joins import GroupStream

    return (
        DBSession().query(cls).filter(
            sa.literal(
                user_or_token.is_admin)).join(
                    User, cls.user).outerjoin(
                        Group, User.groups).outerjoin(
                            GroupStream,
                            sa.and_(
                                GroupStream.group_id == Group.id,
                                GroupStream.stream_id == cls.stream_id,
                            ),
                        ).group_by(cls.id)
        # no OR here because Users will always be a member of at least one
        # group -- their single user group.
        .having(sa.func.bool_and(GroupStream.stream_id.is_(None))))
Exemplo n.º 11
0
    def area(self):
        """Integrated area in sq. deg within localization."""

        union = (sa.select(
            ha.func.union(
                InstrumentFieldTile.healpix).label('healpix')).filter(
                    InstrumentFieldTile.instrument_field_id ==
                    PlannedObservation.field_id,
                    PlannedObservation.observation_plan_id == self.id,
                ).subquery())

        area = sa.func.sum(union.columns.healpix.area)
        query_area = sa.select(area).filter(
            LocalizationTile.localization_id ==
            self.observation_plan_request.localization_id,
            union.columns.healpix.overlaps(LocalizationTile.healpix),
        )
        intarea = DBSession().execute(query_area).scalar_one()

        if intarea is None:
            intarea = 0.0
        return intarea * (180.0 / np.pi)**2
Exemplo n.º 12
0
    def probability(self):
        """Integrated probability within a given localization."""

        union = (sa.select(
            ha.func.union(
                InstrumentFieldTile.healpix).label('healpix')).filter(
                    InstrumentFieldTile.instrument_field_id ==
                    PlannedObservation.field_id,
                    PlannedObservation.observation_plan_id == self.id,
                ).subquery())

        prob = sa.func.sum(
            LocalizationTile.probdensity *
            (union.columns.healpix * LocalizationTile.healpix).area)
        query_prob = sa.select(prob).filter(
            LocalizationTile.localization_id ==
            self.observation_plan_request.localization_id,
            union.columns.healpix.overlaps(LocalizationTile.healpix),
        )
        intprob = DBSession().execute(query_prob).scalar_one()
        if intprob is None:
            intprob = 0.0

        return intprob
Exemplo n.º 13
0
def send_source_notification(mapper, connection, target):
    app_base_url = get_app_base_url()

    link_location = f'{app_base_url}/source/{target.source_id}'
    if target.sent_by.first_name is not None and target.sent_by.last_name is not None:
        sent_by_name = f'{target.sent_by.first_name} {target.sent_by.last_name}'
    else:
        sent_by_name = target.sent_by.username

    group_ids = map(lambda group: group.id, target.groups)
    groups = DBSession().query(Group).filter(Group.id.in_(group_ids)).all()

    target_users = set()
    for group in groups:
        # Use a set to get unique iterable of users
        target_users.update(group.users)

    source = DBSession().query(Obj).get(target.source_id)
    source_info = ""
    if source.ra is not None:
        source_info += f'RA={source.ra} '
    if source.dec is not None:
        source_info += f'Dec={source.dec}'
    source_info = source_info.strip()

    # Send SMS messages to opted-in users if desired
    if target.level == "hard":
        message_text = (
            f'{cfg["app.title"]}: {sent_by_name} would like to call your immediate'
            f' attention to a source at {link_location} ({source_info}).')
        if target.additional_notes != "" and target.additional_notes is not None:
            message_text += f' Addtional notes: {target.additional_notes}'

        account_sid = cfg["twilio.sms_account_sid"]
        auth_token = cfg["twilio.sms_auth_token"]
        from_number = cfg["twilio.from_number"]
        client = TwilioClient(account_sid, auth_token)
        for user in target_users:
            # If user has a phone number registered and opted into SMS notifications
            if (user.contact_phone is not None and user.preferences is not None
                    and "allowSMSAlerts" in user.preferences
                    and user.preferences.get("allowSMSAlerts")):
                client.messages.create(body=message_text,
                                       from_=from_number,
                                       to=user.contact_phone.e164)

    # Send email notifications
    recipients = []
    for user in target_users:
        # If user has a contact email registered and opted into email notifications
        if (user.contact_email is not None and user.preferences is not None
                and "allowEmailAlerts" in user.preferences
                and user.preferences.get("allowEmailAlerts")):
            recipients.append(user.contact_email)

    descriptor = "immediate" if target.level == "hard" else ""
    html_content = (
        f'{sent_by_name} would like to call your {descriptor} attention to'
        f' <a href="{link_location}">{target.source_id}</a> ({source_info})')
    if target.additional_notes != "" and target.additional_notes is not None:
        html_content += f'<br /><br />Additional notes: {target.additional_notes}'

    if len(recipients) > 0:
        send_email(
            recipients=recipients,
            subject=f'{cfg["app.title"]}: Source Alert',
            body=html_content,
        )
Exemplo n.º 14
0
def delete_obj_if_all_data_owned(cls, user_or_token):
    from .source import Source

    allow_nonadmins = cfg["misc.allow_nonadmins_delete_objs"] or False

    deletable_photometry = Photometry.query_records_accessible_by(
        user_or_token, mode="delete"
    ).subquery()
    nondeletable_photometry = (
        DBSession()
        .query(Photometry.obj_id)
        .join(
            deletable_photometry,
            deletable_photometry.c.id == Photometry.id,
            isouter=True,
        )
        .filter(deletable_photometry.c.id.is_(None))
        .distinct(Photometry.obj_id)
        .subquery()
    )

    deletable_spectra = Spectrum.query_records_accessible_by(
        user_or_token, mode="delete"
    ).subquery()
    nondeletable_spectra = (
        DBSession()
        .query(Spectrum.obj_id)
        .join(
            deletable_spectra,
            deletable_spectra.c.id == Spectrum.id,
            isouter=True,
        )
        .filter(deletable_spectra.c.id.is_(None))
        .distinct(Spectrum.obj_id)
        .subquery()
    )

    deletable_candidates = Candidate.query_records_accessible_by(
        user_or_token, mode="delete"
    ).subquery()
    nondeletable_candidates = (
        DBSession()
        .query(Candidate.obj_id)
        .join(
            deletable_candidates,
            deletable_candidates.c.id == Candidate.id,
            isouter=True,
        )
        .filter(deletable_candidates.c.id.is_(None))
        .distinct(Candidate.obj_id)
        .subquery()
    )

    deletable_sources = Source.query_records_accessible_by(
        user_or_token, mode="delete"
    ).subquery()
    nondeletable_sources = (
        DBSession()
        .query(Source.obj_id)
        .join(
            deletable_sources,
            deletable_sources.c.id == Source.id,
            isouter=True,
        )
        .filter(deletable_sources.c.id.is_(None))
        .distinct(Source.obj_id)
        .subquery()
    )

    return (
        DBSession()
        .query(cls)
        .join(
            nondeletable_photometry,
            nondeletable_photometry.c.obj_id == cls.id,
            isouter=True,
        )
        .filter(nondeletable_photometry.c.obj_id.is_(None))
        .join(
            nondeletable_spectra,
            nondeletable_spectra.c.obj_id == cls.id,
            isouter=True,
        )
        .filter(nondeletable_spectra.c.obj_id.is_(None))
        .join(
            nondeletable_candidates,
            nondeletable_candidates.c.obj_id == cls.id,
            isouter=True,
        )
        .filter(nondeletable_candidates.c.obj_id.is_(None))
        .join(
            nondeletable_sources,
            nondeletable_sources.c.obj_id == cls.id,
            isouter=True,
        )
        .filter(nondeletable_sources.c.obj_id.is_(None))
        .filter(sa.literal(allow_nonadmins))
    )
Exemplo n.º 15
0
def get_users(role=None):
    return [user[0] for user in DBSession().execute(sa.select(User))]
Exemplo n.º 16
0
 def receive_after_flush(session, context):
     single_user_group = target.single_user_group
     single_user_group.name = slugify(target.username)
     DBSession().merge(single_user_group)
Exemplo n.º 17
0
 def receive_after_flush(session, context):
     DBSession().delete(single_user_group)
Exemplo n.º 18
0
@property
def user_or_token_accessible_streams(self):
    """Return the list of Streams a User or Token has access to."""
    if "System admin" in self.permissions:
        return Stream.query.all()
    if isinstance(self, Token):
        return self.created_by.streams
    return self.streams


User.to_dict = user_to_dict
User.accessible_groups = user_or_token_accessible_groups
User.accessible_streams = user_or_token_accessible_streams
User.single_user_group = property(lambda self: DBSession().query(Group).join(
    GroupUser).filter(Group.single_user_group.is_(True), GroupUser.user_id ==
                      self.id).first())
User.streams = relationship(
    'Stream',
    secondary='stream_users',
    back_populates='users',
    passive_deletes=True,
    doc="The Streams this User has access to.",
)
User.groups = relationship(
    'Group',
    secondary='group_users',
    back_populates='users',
    passive_deletes=True,
    doc="The Groups this User is a member of.",
)
Exemplo n.º 19
0
        script = job["script"]
        limit = job.get("limit")

        key = f"{script}+{interval}"

        if tc.should_run(key, interval, limit=limit):
            log(f"Executing {script}")
            tc.reset(key)
            try:
                proc = subprocess.Popen(
                    [script, *sys.argv[1:]],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                )
                output, _ = proc.communicate()
            except Exception as e:
                log(f"Error executing {script}: {e}")
                DBSession().add(
                    CronJobRun(script=script, exit_status=1, output=str(e)))
            else:
                DBSession().add(
                    CronJobRun(
                        script=script,
                        exit_status=proc.returncode,
                        output=output.decode("utf-8").strip(),
                    ))
            finally:
                DBSession().commit()

    time.sleep(60)
Exemplo n.º 20
0
)
GroupUser.can_save = sa.Column(
    sa.Boolean,
    nullable=False,
    server_default="true",
    doc=
    "Boolean flag indicating whether the user should be able to save sources to the group",
)
GroupUser.update = CustomUserAccessControl(groupuser_update_access_logic)
GroupUser.delete = (
    # users can remove themselves from a group
    # admins can remove users from a group
    # no one can remove a user from their single user group
    (accessible_by_group_admins | AccessibleIfUserMatches('user'))
    & GroupUser.read
    & CustomUserAccessControl(lambda cls, user_or_token: DBSession().query(
        cls).join(Group).filter(Group.single_user_group.is_(False))))


def group_create_logic(cls, user_or_token):
    """
    Can only add a user to a group if they have all the requisite
    streams required for entry to the group. And users cannot
    be added to single user groups through the Groups API (only
    through event handlers).
    """
    from .stream import Stream, StreamUser

    return (DBSession().query(cls).join(Group).outerjoin(
        Stream, Group.streams).outerjoin(
            StreamUser,
            sa.and_(
Exemplo n.º 21
0
GroupStream = join_model('group_streams', Group, Stream)
GroupStream.__doc__ = "Join table mapping Groups to Streams."
GroupStream.update = restricted
GroupStream.delete = (
    # only admins can delete streams from groups
    accessible_by_group_admins
    & GroupStream.read
) & CustomUserAccessControl(
    # Can only delete a stream from the group if none of the group's filters
    # are operating on the stream.
    lambda cls, user_or_token: DBSession().query(cls).outerjoin(Stream).
    outerjoin(
        Filter,
        sa.and_(Filter.stream_id == Stream.id, Filter.group_id == cls.group_id
                ),
    ).group_by(cls.id).having(
        sa.or_(
            sa.func.bool_and(Filter.id.is_(None)),
            sa.func.bool_and(Stream.id.is_(None)),  # group has no streams
        )))
GroupStream.create = (
    # only admins can add streams to groups
    accessible_by_group_admins
    & GroupStream.read
    & CustomUserAccessControl(
        # Can only add a stream to a group if all users in the group have
        # access to the stream.
        # Also, cannot add stream access to single user groups.
        lambda cls, user_or_token: DBSession().query(cls).
        join(Group, cls.group).outerjoin(User, Group.users).outerjoin(
            StreamUser,
Exemplo n.º 22
0
def add_user_notifications(mapper, connection, target):
    # Add front-end user notifications
    @event.listens_for(DBSession(), "after_flush", once=True)
    def receive_after_flush(session, context):

        is_gcnnotice = "dateobs" in target.to_dict()
        is_facility_transaction = "initiator_id" in target.to_dict()

        if is_gcnnotice:
            users = User.query.filter(User.preferences["slack_integration"]
                                      ["gcnnotices"].astext.cast(
                                          sa.Boolean).is_(True)).all()
        elif is_facility_transaction:
            users = User.query.filter(User.preferences["slack_integration"]
                                      ["facilitytransactions"].astext.cast(
                                          sa.Boolean).is_(True)).all()
        else:
            listing_subquery = (Listing.query.filter(
                Listing.list_name == "favorites").filter(
                    Listing.obj_id == target.obj_id).distinct(
                        Listing.user_id).subquery())
            users = (User.query.join(
                listing_subquery,
                User.id == listing_subquery.c.user_id).filter(
                    User.preferences["favorite_sources_activity_notifications"]
                    [target.__tablename__].astext.cast(
                        sa.Boolean).is_(True)).all())
        ws_flow = Flow()
        for user in users:
            # Only notify users who have read access to the new record in question
            if target.__class__.get_if_accessible_by(target.id,
                                                     user) is not None:
                if is_gcnnotice:
                    session.add(
                        UserNotification(
                            user=user,
                            text=
                            f"New {target.__class__.__name__.lower()} on GcnEvent *{target.dateobs}*",
                            url=
                            f"/gcn_events/{str(target.dateobs).replace(' ','T')}",
                        ))
                elif is_facility_transaction:
                    if "observation_plan_request" in target.to_dict():
                        allocation_id = target.observation_plan_request.allocation_id
                        allocation = session.query(Allocation).get(
                            allocation_id)
                        instrument = allocation.instrument
                        localization_id = (
                            target.observation_plan_request.localization_id)
                        localization = session.query(Localization).get(
                            localization_id)
                        session.add(
                            UserNotification(
                                user=user,
                                text=
                                f"New observation plan submission for GcnEvent *{localization.dateobs}* by *{instrument.name}*",
                                url=
                                f"/gcn_events/{str(localization.dateobs).replace(' ','T')}",
                            ))
                    elif "followup_request" in target.to_dict():
                        allocation_id = target.followup_request.allocation_id
                        allocation = session.query(Allocation).get(
                            allocation_id)
                        instrument = allocation.instrument
                        session.add(
                            UserNotification(
                                user=user,
                                text=
                                f"New follow-up submission for object *{target.followup_request.obj_id}* by *{instrument.name}*",
                                url=f"/source/{target.followup_request.obj_id}",
                            ))

                else:
                    session.add(
                        UserNotification(
                            user=user,
                            text=
                            f"New {target.__class__.__name__.lower()} on your favorite source *{target.obj_id}*",
                            url=f"/source/{target.obj_id}",
                        ))
                ws_flow.push(user.id, "skyportal/FETCH_NOTIFICATIONS")
Exemplo n.º 23
0
def get_single_user_group(self):
    group = (DBSession().scalars(
        sa.select(Group).join(GroupUser).where(
            Group.single_user_group.is_(True),
            GroupUser.user_id == self.id)).first())
    return group
Exemplo n.º 24
0
 def tags(cls):
     """List of tags."""
     return (DBSession().query(
         GcnTag.text).filter(GcnTag.dateobs == cls.dateobs).subquery())