diff options
author | Max Meinhold <mxmeinhold@gmail.com> | 2021-01-26 12:27:14 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-26 12:27:14 -0500 |
commit | e4d19a78c6bc1c029d306ebb0276f821cdbd424c (patch) | |
tree | 8d2d4ed578b87e899719644e2dc0c7414dc5b9a3 | |
parent | 5e09efe83ee3656efdaac9155fffa351ef403175 (diff) | |
parent | 9a706d2bccb32ae7bd280daaae15d24dfa0c1df2 (diff) |
Merge pull request #236 from ComputerScienceHouse/developv3.5.2
-rw-r--r-- | .github/dependabot.yml | 16 | ||||
-rw-r--r-- | .github/workflows/docker-build.yml | 32 | ||||
-rw-r--r-- | .github/workflows/node-js.yml | 27 | ||||
-rw-r--r-- | .github/workflows/python-app.yml | 34 | ||||
-rw-r--r-- | .travis.yml | 16 | ||||
-rw-r--r-- | Dockerfile | 2 | ||||
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | config.env.py | 17 | ||||
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | packet/__init__.py | 34 | ||||
-rw-r--r-- | packet/context_processors.py | 5 | ||||
-rw-r--r-- | packet/ldap.py | 413 | ||||
-rw-r--r-- | packet/log_utils.py | 5 | ||||
-rw-r--r-- | packet/notifications.py | 19 | ||||
-rw-r--r-- | packet/routes/api.py | 9 | ||||
-rw-r--r-- | packet/templates/packet.html | 2 | ||||
-rw-r--r-- | packet/utils.py | 52 | ||||
-rw-r--r-- | requirements.txt | 16 | ||||
-rw-r--r-- | yarn.lock | 6 |
19 files changed, 469 insertions, 256 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..94a31c7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "npm" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..d929520 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,32 @@ +name: Docker + +on: + push: + branches: + - develop + - master + + # Run tests for any PRs. + pull_request: + branches: + - develop + - master + +env: + IMAGE_NAME: packet + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Run tests + run: | + if [ -f docker-compose.test.yml ]; then + docker-compose --file docker-compose.test.yml build + docker-compose --file docker-compose.test.yml run sut + else + docker build . --file Dockerfile + fi diff --git a/.github/workflows/node-js.yml b/.github/workflows/node-js.yml new file mode 100644 index 0000000..c2f0af6 --- /dev/null +++ b/.github/workflows/node-js.yml @@ -0,0 +1,27 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: [develop, master] + pull_request: + branches: [develop, master] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn install + - run: yarn prod diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..71dcdd5 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,34 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + +jobs: + lint: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.7, 3.8] + + steps: + - name: Install ldap dependencies + run: sudo apt-get install libldap2-dev libsasl2-dev + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with pylint + run: | + pylint packet diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d0cbb08..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: python -python: - - "3.7" -services: - - "docker" - -install: - - "pip install -r requirements.txt" - - "curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.3/install.sh | bash" - - "nvm install" - - "nvm use" - - "npm install -g gulp" - - "npm install" -script: - - "gulp lint" - - "docker build -t packet ." @@ -2,7 +2,7 @@ FROM python:3.7-slim-buster MAINTAINER Devin Matte <matted@csh.rit.edu> RUN apt-get -yq update && \ - apt-get -yq --no-install-recommends install gcc curl libsasl2-dev libldap2-dev libssl-dev && \ + apt-get -yq --no-install-recommends install gcc curl libsasl2-dev libldap2-dev libssl-dev gnupg2 && \ apt-get -yq clean all RUN mkdir /opt/packet @@ -47,6 +47,20 @@ If it doesn't work for some reason, you may have to globally install gulp throug npm install -g gulp ``` +### Local Development +* PostgreSQL +You'll need a postgres instance to use as a development DB. +You can use an existing database, like the instance used for the dev branch, use a database on another server, or spin up a container using docker or podman. +To get setup using docker, run +```bash +docker run --name packet-postgres -e POSTGRES_PASSWORD=mysecretpassword -d -p 5432:5432 postgres +``` +After the container starts up, you should be able to connect with the connection string `postgresql://postgres:mysecretpassword@localhost:5432/postgres`, which is the default connection string in `config.env.py`. +Once the container is up, run the following to set up the database tables. +```bash +flask db upgrade +``` + ### Secrets and configuration Packet supports 2 primary configuration methods: 1. Environment variables - See `config.env.py` for the expected names and default values. diff --git a/config.env.py b/config.env.py index 02125fa..b14e3eb 100644 --- a/config.env.py +++ b/config.env.py @@ -3,7 +3,7 @@ Default configuration settings and environment variable based configuration logi See the readme for more information """ from distutils.util import strtobool -from os import environ +from os import environ, path, getcwd # Flask config DEBUG = False @@ -25,12 +25,25 @@ OIDC_CLIENT_ID = environ.get("PACKET_OIDC_CLIENT_ID", "packet") OIDC_CLIENT_SECRET = environ.get("PACKET_OIDC_CLIENT_SECRET", "PLEASE_REPLACE_ME") # SQLAlchemy config -SQLALCHEMY_DATABASE_URI = environ.get("PACKET_DATABASE_URI", None) +SQLALCHEMY_DATABASE_URI = environ.get("PACKET_DATABASE_URI", "postgresql://postgres:mysecretpassword@localhost:5432/postgres") SQLALCHEMY_TRACK_MODIFICATIONS = False # LDAP config LDAP_BIND_DN = environ.get("PACKET_LDAP_BIND_DN", None) LDAP_BIND_PASS = environ.get("PACKET_LDAP_BIND_PASS", None) +LDAP_MOCK_MEMBERS = [ + {'uid':'evals', 'groups': ['eboard', 'eboard-evaluations', 'active']}, + {'uid':'imps-3da', 'groups': ['eboard', 'eboard-imps', '3da', 'active']}, + { + 'uid':'rtp-cm-webs-onfloor', + 'groups': ['active-rtp', 'rtp', 'constitutional_maintainers', 'webmaster', 'active', 'onfloor'], + 'room_number': 1024 + }, + {'uid':'misc-rtp', 'groups': ['rtp']}, + {'uid':'onfloor', 'groups': ['active', 'onfloor'], 'room_number': 1024}, + {'uid':'active-offfloor', 'groups': ['active']}, + {'uid':'alum', 'groups': ['member']}, + ] # Mail Config MAIL_PROD = strtobool(environ.get("PACKET_MAIL_PROD", "False")) diff --git a/package.json b/package.json index b97168d..17a3c6c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "title": "CSH Packet", "name": "csh-packet", - "version": "3.5.1", + "version": "3.5.2", "description": "A web app implementation of the CSH introductory packet.", "bugs": { "url": "https://github.com/ComputerScienceHouse/packet/issues", @@ -13,6 +13,10 @@ "Joel Eager (https://github.com/JoelEager)", "Max Meinhold (https://github.com/mxmeinhold)" ], + "scripts": { + "prod": "gulp production", + "lint": "gulp lint" + }, "repository": { "type": "git", "url": "https://github.com/ComputerScienceHouse/packet.git" diff --git a/packet/__init__.py b/packet/__init__.py index 32e9fe7..89b7728 100644 --- a/packet/__init__.py +++ b/packet/__init__.py @@ -50,19 +50,31 @@ APP_CONFIG = ProviderConfiguration(issuer=app.config['OIDC_ISSUER'], app.config['OIDC_CLIENT_SECRET'])) # Initialize Onesignal Notification apps -csh_onesignal_client = onesignal.Client(user_auth_key=app.config['ONESIGNAL_USER_AUTH_KEY'], - app_auth_key=app.config['ONESIGNAL_CSH_APP_AUTH_KEY'], - app_id=app.config['ONESIGNAL_CSH_APP_ID']) - -intro_onesignal_client = onesignal.Client(user_auth_key=app.config['ONESIGNAL_USER_AUTH_KEY'], - app_auth_key=app.config['ONESIGNAL_INTRO_APP_AUTH_KEY'], - app_id=app.config['ONESIGNAL_INTRO_APP_ID']) +csh_onesignal_client = None +if app.config['ONESIGNAL_USER_AUTH_KEY'] and \ + app.config['ONESIGNAL_CSH_APP_AUTH_KEY'] and \ + app.config['ONESIGNAL_CSH_APP_ID']: + csh_onesignal_client = onesignal.Client( + user_auth_key=app.config['ONESIGNAL_USER_AUTH_KEY'], + app_auth_key=app.config['ONESIGNAL_CSH_APP_AUTH_KEY'], + app_id=app.config['ONESIGNAL_CSH_APP_ID'] + ) + app.logger.info('CSH Onesignal configured and notifications enabled') + +intro_onesignal_client = None +if app.config['ONESIGNAL_USER_AUTH_KEY'] and \ + app.config['ONESIGNAL_INTRO_APP_AUTH_KEY'] and \ + app.config['ONESIGNAL_INTRO_APP_ID']: + intro_onesignal_client = onesignal.Client( + user_auth_key=app.config['ONESIGNAL_USER_AUTH_KEY'], + app_auth_key=app.config['ONESIGNAL_INTRO_APP_AUTH_KEY'], + app_id=app.config['ONESIGNAL_INTRO_APP_ID'] + ) + app.logger.info('Intro Onesignal configured and notifications enabled') # OIDC Auth auth = OIDCAuthentication({'app': APP_CONFIG}, app) - -# LDAP -_ldap = csh_ldap.CSHLDAP(app.config['LDAP_BIND_DN'], app.config['LDAP_BIND_PASS']) +app.logger.info('OIDCAuth configured') # Sentry sentry_sdk.init( @@ -70,9 +82,9 @@ sentry_sdk.init( integrations=[FlaskIntegration(), SqlalchemyIntegration()] ) -app.logger.info('OIDCAuth and LDAP configured') # pylint: disable=wrong-import-position +from .ldap import ldap from . import models from . import context_processors from . import commands diff --git a/packet/context_processors.py b/packet/context_processors.py index 2e8d5c5..bff75b1 100644 --- a/packet/context_processors.py +++ b/packet/context_processors.py @@ -6,16 +6,15 @@ import urllib from functools import lru_cache from datetime import datetime -from packet.ldap import ldap_get_member from packet.models import Freshman -from packet import app +from packet import app, ldap # pylint: disable=bare-except @lru_cache(maxsize=128) def get_csh_name(username): try: - member = ldap_get_member(username) + member = ldap.get_member(username) return member.cn + ' (' + member.uid + ')' except: return username diff --git a/packet/ldap.py b/packet/ldap.py index 0f78e17..4fcb738 100644 --- a/packet/ldap.py +++ b/packet/ldap.py @@ -5,197 +5,260 @@ Helper functions for working with the csh_ldap library from functools import lru_cache from datetime import date -from packet import _ldap +from csh_ldap import CSHLDAP +from packet import app -def _ldap_get_group_members(group): - """ - :return: A list of CSHMember instances - """ - return _ldap.get_group(group).get_members() +class MockMember: -def _ldap_is_member_of_group(member, group): - """ - :param member: A CSHMember instance - """ - for group_dn in member.get('memberOf'): - if group == group_dn.split(',')[0][3:]: - return True + def __init__(self, uid: str, groups: list = None, cn: str = None, room_number: int = None): + self.uid = uid + self.groups = groups if groups else list() + if room_number: + self.room_number = room_number + self.cn = cn if cn else uid.title() # pylint: disable=invalid-name - return False + def __eq__(self, other): + if type(other) is type(self): + return self.uid == other.uid + return False -# Getters -@lru_cache(maxsize=256) -def ldap_get_member(username): - """ - :return: A CSHMember instance - """ - return _ldap.get_member(username, uid=True) + def __hash__(self): + return hash(self.uid) -def ldap_get_active_members(): - """ - Gets all current, dues-paying members - :return: A list of CSHMember instances - """ - return _ldap_get_group_members('active') + def __repr__(self): + return f'MockMember(uid: {self.uid}, groups: {self.groups})' -def ldap_get_intro_members(): - """ - Gets all freshmen members - :return: A list of CSHMember instances - """ - return _ldap_get_group_members('intromembers') - - -def ldap_get_eboard(): - """ - Gets all voting members of eboard - :return: A list of CSHMember instances - """ - members = _ldap_get_group_members('eboard-chairman') + _ldap_get_group_members('eboard-evaluations' - ) + _ldap_get_group_members('eboard-financial') + _ldap_get_group_members('eboard-history' - ) + _ldap_get_group_members('eboard-imps') + _ldap_get_group_members('eboard-opcomm' - ) + _ldap_get_group_members('eboard-research') + _ldap_get_group_members('eboard-social' - ) - - return members - - -def ldap_get_live_onfloor(): - """ - All upperclassmen who live on floor and are not eboard - :return: A list of CSHMember instances - """ - members = [] - onfloor = _ldap_get_group_members('onfloor') - for member in onfloor: - if ldap_get_roomnumber(member) and not ldap_is_eboard(member): - members.append(member) +class LDAPWrapper: - return members + def __init__(self, cshldap=None, mock_members=None): + self.ldap = cshldap + self.mock_members = mock_members + if self.ldap: + app.logger.info('LDAP configured with CSH LDAP') + else: + app.logger.info('LDAP configured with local mock') -def ldap_get_active_rtps(): - """ - All active RTPs - :return: A list of CSHMember instances - """ - return [member.uid for member in _ldap_get_group_members('active_rtp')] + def _get_group_members(self, group): + """ + :return: A list of CSHMember instances + """ + if self.ldap: + return self.ldap.get_group(group).get_members() + else: + return list(filter(lambda member: group in member.groups, self.mock_members)) -def ldap_get_3das(): - """ - All 3das - :return: A list of CSHMember instances - """ - return [member.uid for member in _ldap_get_group_members('3da')] + def _is_member_of_group(self, member, group): + """ + :param member: A CSHMember instance + """ + if self.ldap: + for group_dn in member.get('memberOf'): + if group == group_dn.split(',')[0][3:]: + return True + return False + else: + return group in member.groups -def ldap_get_webmasters(): - """ - All webmasters - :return: A list of CSHMember instances - """ - return [member.uid for member in _ldap_get_group_members('webmaster')] - - -def ldap_get_constitutional_maintainers(): - """ - All constitutional maintainers - :return: A list of CSHMember instances - """ - return [member.uid for member in _ldap_get_group_members('constitutional_maintainers')] - - -def ldap_get_wiki_maintainers(): - """ - All wiki maintainers - :return: A list of CSHMember instances - """ - return [member.uid for member in _ldap_get_group_members('wiki_maintainers')] - - -def ldap_get_drink_admins(): - """ - All drink admins - :return: A list of CSHMember instances - """ - return [member.uid for member in _ldap_get_group_members('drink')] - - -def ldap_get_eboard_role(member): - """ - :param member: A CSHMember instance - :return: A String or None - """ - - return_val = None - - if _ldap_is_member_of_group(member, 'eboard-chairman'): - return_val = 'Chairman' - elif _ldap_is_member_of_group(member, 'eboard-evaluations'): - return_val = 'Evals' - elif _ldap_is_member_of_group(member, 'eboard-financial'): - return_val = 'Financial' - elif _ldap_is_member_of_group(member, 'eboard-history'): - return_val = 'History' - elif _ldap_is_member_of_group(member, 'eboard-imps'): - return_val = 'Imps' - elif _ldap_is_member_of_group(member, 'eboard-opcomm'): - return_val = 'OpComm' - elif _ldap_is_member_of_group(member, 'eboard-research'): - return_val = 'R&D' - elif _ldap_is_member_of_group(member, 'eboard-social'): - return_val = 'Social' - elif _ldap_is_member_of_group(member, 'eboard-secretary'): - return_val = 'Secretary' - - return return_val - - -# Status checkers -def ldap_is_eboard(member): - """ - :param member: A CSHMember instance - """ - return _ldap_is_member_of_group(member, 'eboard') - - -def ldap_is_evals(member): - return _ldap_is_member_of_group(member, 'eboard-evaluations') - - -def ldap_is_rtp(member): - return _ldap_is_member_of_group(member, 'rtp') - - -def ldap_is_intromember(member): - """ - :param member: A CSHMember instance - """ - return _ldap_is_member_of_group(member, 'intromembers') - - -def ldap_is_on_coop(member): - """ - :param member: A CSHMember instance - """ - if date.today().month > 6: - return _ldap_is_member_of_group(member, 'fall_coop') - else: - return _ldap_is_member_of_group(member, 'spring_coop') - - -def ldap_get_roomnumber(member): - """ - :param member: A CSHMember instance - """ - try: - return member.roomNumber - except AttributeError: - return None + # Getters + + @lru_cache(maxsize=256) + def get_member(self, username): + """ + :return: A CSHMember instance + """ + if self.ldap: + return self.ldap.get_member(username, uid=True) + else: + member = next(filter(lambda member: member.uid == username, self.mock_members), None) + if member: + return member + raise KeyError('Invalid Search Name') + + + def get_active_members(self): + """ + Gets all current, dues-paying members + :return: A list of CSHMember instances + """ + return self._get_group_members('active') + + + def get_intro_members(self): + """ + Gets all freshmen members + :return: A list of CSHMember instances + """ + return self._get_group_members('intromembers') + + + def get_eboard(self): + """ + Gets all voting members of eboard + :return: A list of CSHMember instances + """ + members = self._get_group_members('eboard-chairman') + self._get_group_members('eboard-evaluations' + ) + self._get_group_members('eboard-financial') + self._get_group_members('eboard-history' + ) + self._get_group_members('eboard-imps') + self._get_group_members('eboard-opcomm' + ) + self._get_group_members('eboard-research') + self._get_group_members('eboard-social' + ) + + return members + + + def get_live_onfloor(self): + """ + All upperclassmen who live on floor and are not eboard + :return: A list of CSHMember instances + """ + members = [] + onfloor = self._get_group_members('onfloor') + for member in onfloor: + if self.get_roomnumber(member) and not self.is_eboard(member): + members.append(member) + + return members + + + def get_active_rtps(self): + """ + All active RTPs + :return: A list of CSHMember instances + """ + return [member.uid for member in self._get_group_members('active_rtp')] + + + def get_3das(self): + """ + All 3das + :return: A list of CSHMember instances + """ + return [member.uid for member in self._get_group_members('3da')] + + + def get_webmasters(self): + """ + All webmasters + :return: A list of CSHMember instances + """ + return [member.uid for member in self._get_group_members('webmaster')] + + + def get_constitutional_maintainers(self): + """ + All constitutional maintainers + :return: A list of CSHMember instances + """ + return [member.uid for member in self._get_group_members('constitutional_maintainers')] + + def get_wiki_maintainers(self): + """ + All wiki maintainers + :return: A list of CSHMember instances + """ + return [member.uid for member in self._get_group_members('wiki_maintainers')] + + + def get_drink_admins(self): + """ + All drink admins + :return: A list of CSHMember instances + """ + return [member.uid for member in self._get_group_members('drink')] + + + def get_eboard_role(self, member): + """ + :param member: A CSHMember instance + :return: A String or None + """ + + return_val = None + + if self._is_member_of_group(member, 'eboard-chairman'): + return_val = 'Chairperson' + elif self._is_member_of_group(member, 'eboard-evaluations'): + return_val = 'Evals' + elif self._is_member_of_group(member, 'eboard-financial'): + return_val = 'Financial' + elif self._is_member_of_group(member, 'eboard-history'): + return_val = 'History' + elif self._is_member_of_group(member, 'eboard-imps'): + return_val = 'Imps' + elif self._is_member_of_group(member, 'eboard-opcomm'): + return_val = 'OpComm' + elif self._is_member_of_group(member, 'eboard-research'): + return_val = 'R&D' + elif self._is_member_of_group(member, 'eboard-social'): + return_val = 'Social' + elif self._is_member_of_group(member, 'eboard-secretary'): + return_val = 'Secretary' + + return return_val + + + # Status checkers + def is_eboard(self, member): + """ + :param member: A CSHMember instance + """ + return self._is_member_of_group(member, 'eboard') + + + def is_evals(self, member): + return self._is_member_of_group(member, 'eboard-evaluations') + + + def is_rtp(self, member): + return self._is_member_of_group(member, 'rtp') + + + def is_intromember(self, member): + """ + :param member: A CSHMember instance + """ + return self._is_member_of_group(member, 'intromembers') + + + def is_on_coop(self, member): + """ + :param member: A CSHMember instance + """ + if date.today().month > 6: + return self._is_member_of_group(member, 'fall_coop') + else: + return self._is_member_of_group(member, 'spring_coop') + + + def get_roomnumber(self, member): # pylint: disable=no-self-use + """ + :param member: A CSHMember instance + """ + try: + return member.roomNumber + except AttributeError: + return None + + +if app.config['LDAP_BIND_DN'] and app.config['LDAP_BIND_PASS']: + ldap = LDAPWrapper(cshldap=CSHLDAP(app.config['LDAP_BIND_DN'], + app.config['LDAP_BIND_PASS'] + ) +) +else: + ldap = LDAPWrapper( + mock_members=list( + map( + lambda mock_dict: MockMember(**mock_dict), + app.config['LDAP_MOCK_MEMBERS'] + ) + ) + ) diff --git a/packet/log_utils.py b/packet/log_utils.py index 66c1453..5481bef 100644 --- a/packet/log_utils.py +++ b/packet/log_utils.py @@ -5,9 +5,8 @@ General utilities for logging metadata from functools import wraps from datetime import datetime -from packet import app +from packet import app, ldap from packet.context_processors import get_rit_name -from packet.ldap import ldap_get_member from packet.utils import is_freshman_on_floor @@ -39,7 +38,7 @@ def _format_cache(func): # Tuple of lru_cache functions to log stats from -_caches = (get_rit_name, ldap_get_member, is_freshman_on_floor) +_caches = (get_rit_name, ldap.get_member, is_freshman_on_floor) def log_cache(func): diff --git a/packet/notifications.py b/packet/notifications.py index a3a93f2..e718265 100644 --- a/packet/notifications.py +++ b/packet/notifications.py @@ -11,6 +11,20 @@ post_body = { 'url': app.config['PROTOCOL'] + app.config['SERVER_NAME'] } +def require_onesignal_intro(func): + def require_onesignal_intro_wrapper(*args, **kwargs): + if intro_onesignal_client: + return func(*args, **kwargs) + return None + return require_onesignal_intro_wrapper + +def require_onesignal_csh(func): + def require_onesignal_csh_wrapper(*args, **kwargs): + if csh_onesignal_client: + return func(*args, **kwargs) + return None + return require_onesignal_csh_wrapper + def send_notification(notification_body, subscriptions, client): tokens = list(map(lambda subscription: subscription.token, subscriptions)) @@ -24,6 +38,7 @@ def send_notification(notification_body, subscriptions, client): app.logger.warn('The notification ({}) was unsuccessful'.format(notification.post_body)) +@require_onesignal_intro def packet_signed_notification(packet, signer): subscriptions = NotificationSubscription.query.filter_by(freshman_username=packet.freshman_username) if subscriptions: @@ -36,6 +51,8 @@ def packet_signed_notification(packet, signer): send_notification(notification_body, subscriptions, intro_onesignal_client) +@require_onesignal_csh +@require_onesignal_intro def packet_100_percent_notification(packet): member_subscriptions = NotificationSubscription.query.filter(NotificationSubscription.member.isnot(None)) intro_subscriptions = NotificationSubscription.query.filter(NotificationSubscription.freshman_username.isnot(None)) @@ -50,6 +67,7 @@ def packet_100_percent_notification(packet): send_notification(notification_body, intro_subscriptions, intro_onesignal_client) +@require_onesignal_intro def packet_starting_notification(packet): subscriptions = NotificationSubscription.query.filter_by(freshman_username=packet.freshman_username) if subscriptions: @@ -62,6 +80,7 @@ def packet_starting_notification(packet): send_notification(notification_body, subscriptions, intro_onesignal_client) +@require_onesignal_csh def packets_starting_notification(start_date): member_subscriptions = NotificationSubscription.query.filter(NotificationSubscription.member.isnot(None)) if member_subscriptions: diff --git a/packet/routes/api.py b/packet/routes/api.py index 828ac96..ee582ce 100644 --- a/packet/routes/api.py +++ b/packet/routes/api.py @@ -6,9 +6,8 @@ from json import dumps from flask import session, request -from packet import app, db +from packet import app, db, ldap from packet.context_processors import get_rit_name -from packet.ldap import _ldap_is_member_of_group, ldap_get_member from packet.log_utils import log_time from packet.mail import send_report_mail from packet.utils import before_request, packet_auth, notify_slack, sync_freshman as sync_freshman_list, \ @@ -42,7 +41,7 @@ def sync_freshman(): # Only allow evals to create new frosh username = str(session['userinfo'].get('preferred_username', '')) - if not _ldap_is_member_of_group(ldap_get_member(username), 'eboard-evaluations'): + if not ldap.is_evals(ldap.get_member(username)): return 'Forbidden: not Evaluations Director', 403 freshmen_in_post = {freshman.rit_username: freshman for freshman in map(POSTFreshman, request.json)} @@ -71,7 +70,7 @@ def create_packet(): # Only allow evals to create new packets username = str(session['userinfo'].get('preferred_username', '')) - if not _ldap_is_member_of_group(ldap_get_member(username), 'eboard-evaluations'): + if not ldap.is_evals(ldap.get_member(username)): return 'Forbidden: not Evaluations Director', 403 base_date = datetime.strptime(request.json['start_date'], '%m/%d/%Y').date() @@ -89,7 +88,7 @@ def create_packet(): def sync_ldap(): # Only allow evals to sync ldap username = str(session['userinfo'].get('preferred_username', '')) - if not _ldap_is_member_of_group(ldap_get_member(username), 'eboard-evaluations'): + if not ldap.is_evals(ldap.get_member(username)): return 'Forbidden: not Evaluations Director', 403 sync_with_ldap() return dumps('Done'), 201 diff --git a/packet/templates/packet.html b/packet/templates/packet.html index 8e30376..c7ddd5a 100644 --- a/packet/templates/packet.html +++ b/packet/templates/packet.html @@ -1,6 +1,6 @@ {% extends "extend/base.html" %} -{% set packet_end = packet.end.strftime('%m/%d/%Y') %} +{% set packet_end = packet.end.strftime('%m/%d/%Y, %H:%M') %} {% block body %} <div class="container main"> diff --git a/packet/utils.py b/packet/utils.py index 0f979f8..4caa673 100644 --- a/packet/utils.py +++ b/packet/utils.py @@ -7,12 +7,9 @@ from functools import wraps, lru_cache import requests from flask import session, redirect -from packet import auth, app, db +from packet import auth, app, db, ldap from packet.mail import send_start_packet_mail from packet.models import Freshman, FreshSignature, Packet, UpperSignature, MiscSignature -from packet.ldap import ldap_get_member, ldap_is_intromember, ldap_is_evals, ldap_is_on_coop, \ - ldap_get_active_members, ldap_get_active_rtps, ldap_get_3das, ldap_get_wiki_maintainers, ldap_get_webmasters, \ - ldap_get_constitutional_maintainers, ldap_get_drink_admins, ldap_get_eboard_role from packet.notifications import packets_starting_notification, packet_starting_notification INTRO_REALM = 'https://sso.csh.rit.edu/auth/realms/intro' @@ -35,11 +32,11 @@ def before_request(func): 'admin': False # It's always false if frosh } else: - member = ldap_get_member(uid) + member = ldap.get_member(uid) info = { 'realm': 'csh', 'uid': uid, - 'admin': ldap_is_evals(member) + 'admin': ldap.is_evals(member) } kwargs['info'] = info @@ -70,7 +67,7 @@ def packet_auth(func): def wrapped_function(*args, **kwargs): if app.config['REALM'] == 'csh': username = str(session['userinfo'].get('preferred_username', '')) - if ldap_is_intromember(ldap_get_member(username)): + if ldap.is_intromember(ldap.get_member(username)): app.logger.warn('Stopped intro member {} from accessing upperclassmen packet'.format(username)) return redirect(app.config['PROTOCOL'] + app.config['PACKET_INTRO'], code=301) @@ -89,8 +86,8 @@ def admin_auth(func): def wrapped_function(*args, **kwargs): if app.config['REALM'] == 'csh': username = str(session['userinfo'].get('preferred_username', '')) - member = ldap_get_member(username) - if not ldap_is_evals(member): + member = ldap.get_member(username) + if not ldap.is_evals(member): app.logger.warn('Stopped member {} from accessing admin UI'.format(username)) return redirect(app.config['PROTOCOL'] + app.config['PACKET_UPPER'], code=301) else: @@ -160,14 +157,15 @@ def create_new_packets(base_date: date, freshmen_list: dict): print('Fetching data from LDAP...') all_upper = list(filter( - lambda member: not ldap_is_intromember(member) and not ldap_is_on_coop(member), ldap_get_active_members())) + lambda member: not ldap.is_intromember(member) and not ldap.is_on_coop(member), ldap.get_active_members())) - rtp = ldap_get_active_rtps() - three_da = ldap_get_3das() - webmaster = ldap_get_webmasters() - c_m = ldap_get_constitutional_maintainers() - w_m = ldap_get_wiki_maintainers() - drink = ldap_get_drink_admins() + + rtp = ldap.get_active_rtps() + three_da = ldap.get_3das() + webmaster = ldap.get_webmasters() + c_m = ldap.get_constitutional_maintainers() + w_m = ldap.get_wiki_maintainers() + drink = ldap.get_drink_admins() # Packet starting notifications packets_starting_notification(start) @@ -182,7 +180,7 @@ def create_new_packets(base_date: date, freshmen_list: dict): for member in all_upper: sig = UpperSignature(packet=packet, member=member.uid) - sig.eboard = ldap_get_eboard_role(member) + sig.eboard = ldap.get_eboard_role(member) sig.active_rtp = member.uid in rtp sig.three_da = member.uid in three_da sig.webmaster = member.uid in webmaster @@ -201,20 +199,20 @@ def create_new_packets(base_date: date, freshmen_list: dict): def sync_with_ldap(): print('Fetching data from LDAP...') all_upper = {member.uid: member for member in filter( - lambda member: not ldap_is_intromember(member) and not ldap_is_on_coop(member), ldap_get_active_members())} + lambda member: not ldap.is_intromember(member) and not ldap.is_on_coop(member), ldap.get_active_members())} - rtp = ldap_get_active_rtps() - three_da = ldap_get_3das() - webmaster = ldap_get_webmasters() - c_m = ldap_get_constitutional_maintainers() - w_m = ldap_get_wiki_maintainers() - drink = ldap_get_drink_admins() + rtp = ldap.get_active_rtps() + three_da = ldap.get_3das() + webmaster = ldap.get_webmasters() + c_m = ldap.get_constitutional_maintainers() + w_m = ldap.get_wiki_maintainers() + drink = ldap.get_drink_admins() print('Applying updates to the DB...') for packet in Packet.query.filter(Packet.end > datetime.now()).all(): # Update the role state of all UpperSignatures for sig in filter(lambda sig: sig.member in all_upper, packet.upper_signatures): - sig.eboard = ldap_get_eboard_role(all_upper[sig.member]) + sig.eboard = ldap.get_eboard_role(all_upper[sig.member]) sig.active_rtp = sig.member in rtp sig.three_da = sig.member in three_da sig.webmaster = sig.member in webmaster @@ -233,7 +231,7 @@ def sync_with_ldap(): for sig in filter(lambda sig: sig.member in all_upper, packet.misc_signatures): MiscSignature.query.filter_by(packet_id=packet.id, member=sig.member).delete() sig = UpperSignature(packet=packet, member=sig.member, signed=True) - sig.eboard = ldap_get_eboard_role(all_upper[sig.member]) + sig.eboard = ldap.get_eboard_role(all_upper[sig.member]) sig.active_rtp = sig.member in rtp sig.three_da = sig.member in three_da sig.webmaster = sig.member in webmaster @@ -247,7 +245,7 @@ def sync_with_ldap(): upper_sigs = set(map(lambda sig: sig.member, packet.upper_signatures)) for member in filter(lambda member: member not in upper_sigs, all_upper): sig = UpperSignature(packet=packet, member=member) - sig.eboard = ldap_get_eboard_role(all_upper[sig.member]) + sig.eboard = ldap.get_eboard_role(all_upper[sig.member]) sig.active_rtp = sig.member in rtp sig.three_da = sig.member in three_da sig.webmaster = sig.member in webmaster diff --git a/requirements.txt b/requirements.txt index d97e06f..01ab4db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ -Flask~=1.1.0 +Flask~=1.1.2 Flask-pyoidc~=2.2.0 Flask-Mail~=0.9.1 Flask-Gzip~=0.2 -flask_sqlalchemy~=2.3.2 -psycopg2-binary~=2.8.3 -Flask-Migrate~=2.2.1 -pylint~=2.3.1 -gunicorn~=19.7.1 -csh_ldap~=2.1.0 +flask_sqlalchemy~=2.4.4 +psycopg2-binary~=2.8.6 +Flask-Migrate~=2.5.3 +pylint~=2.6.0 +gunicorn~=20.0.4 +csh_ldap~=2.3.1 onesignal-sdk~=1.0.0 pylint-quotes~=0.2.1 -sentry-sdk~=0.14.2 +sentry-sdk~=0.19.5 @@ -1544,9 +1544,9 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== interpret@^1.4.0: version "1.4.0" |