Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deployment patch #342

Merged
merged 8 commits into from Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Expand Up @@ -32,7 +32,7 @@ jobs:
key: poetry-0 # increment to reset cache
- name: Install Poetry
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1@v1
uses: snok/install-poetry@v1
with:
version: 1.3.2
virtualenvs-create: true
Expand Down
7 changes: 7 additions & 0 deletions dribdat/apifetch.py
Expand Up @@ -15,6 +15,7 @@
fetch_commits_gitlab,
fetch_commits_gitea,
)
from .utils import sanitize_url
from future.standard_library import install_aliases
install_aliases()

Expand Down Expand Up @@ -211,6 +212,8 @@ def FetchBitbucketProject(project_url):
def FetchDataProject(project_url):
"""Try to load a Data Package formatted JSON file."""
# TODO: use frictionlessdata library!
project_url = project_url.replace('datapackage.json', '')
project_url = sanitize_url(project_url) + 'datapackage.json'
data = requests.get(project_url, timeout=REQUEST_TIMEOUT)
# TODO: treat dribdat events as special
logging.info("Fetching Data Package", project_url)
Expand Down Expand Up @@ -288,6 +291,10 @@ def parse_data_package(json):
def FetchWebProject(project_url):
"""Parse a remote Document, wiki or website URL."""
try:
# TODO: the admin should be able to whitelist a range of allowed
# online resources controlling the domains from which we can
# fetch remote content.
project_url = sanitize_url(project_url)
logging.info("Fetching", project_url)
data = requests.get(project_url, timeout=REQUEST_TIMEOUT)
except requests.exceptions.RequestException:
Expand Down
7 changes: 5 additions & 2 deletions dribdat/apiutils.py
Expand Up @@ -2,13 +2,15 @@
"""Helper functions for the API."""
# Really just a step towards a full API rebuild

from .aggregation import GetEventUsers
from dribdat.user.models import Event, Project, Category, Activity
import io
import csv
import json

from datetime import datetime

from dribdat.user.models import Event, Project, Category, Activity
from dribdat.utils import format_date
from .aggregation import GetEventUsers

from sys import version_info
PY3 = version_info[0] == 3
Expand Down Expand Up @@ -163,3 +165,4 @@ def gen_csv(csvdata):
for rk in rowdata[1:]:
writer.writerow(rk)
return output.getvalue()

38 changes: 19 additions & 19 deletions dribdat/mailer.py
Expand Up @@ -3,27 +3,27 @@
from flask import url_for
from flask_mailman import EmailMessage
from dribdat.utils import random_password # noqa: I005
import logging


async def user_activation(app, user):
def user_activation(user):
"""Send an activation by e-mail."""
act_hash = random_password(32)
with app.app_context():
user.set_hashword(act_hash)
user.save()
base_url = url_for('public.home', _external=True)
act_url = url_for(
'auth.activate',
userid=user.id,
userhash=act_hash,
_external=True)
msg = EmailMessage()
msg.subject = 'Your dribdat account'
msg.body = \
"Thanks for signing up at %s\n\n" % base_url \
+ "Tap here to activate your account:\n\n%s" % act_url
msg.to = [user.email]
app.logger.info('Sending mail to user %d' % user.id)
await msg.send()
return True
user.set_hashword(act_hash)
user.save()
base_url = url_for('public.home', _external=True)
act_url = url_for(
'auth.activate',
userid=user.id,
userhash=act_hash,
_external=True)
msg = EmailMessage()
msg.subject = 'Your dribdat account'
msg.body = \
"Thanks for signing up at %s\n\n" % base_url \
+ "Tap here to activate your account:\n\n%s" % act_url
msg.to = [user.email]
logging.info('Sending mail to user %d' % user.id)
msg.send(fail_silently=True)
return True

5 changes: 2 additions & 3 deletions dribdat/public/api.py
Expand Up @@ -14,7 +14,7 @@
from sqlalchemy import or_

from ..extensions import db, cache
from ..utils import timesince, random_password
from ..utils import timesince, random_password, sanitize_url
from ..decorators import admin_required
from ..user.models import Event, Project, Activity
from ..aggregation import GetProjectData, AddProjectData, GetEventUsers
Expand Down Expand Up @@ -390,12 +390,11 @@ def set_project_values(project, data):

# ------ FRONTEND -------


@blueprint.route('/project/autofill', methods=['GET', 'POST'])
@login_required
def project_autofill():
"""Routine used to help sync project data."""
url = request.args.get('url')
url = sanitize_url(request.args.get('url'))
data = GetProjectData(url)
return jsonify(data)

Expand Down
39 changes: 33 additions & 6 deletions dribdat/public/auth.py
Expand Up @@ -7,9 +7,10 @@
from flask_dance.contrib.slack import slack
from flask_dance.contrib.azure import azure # noqa: I005
from flask_dance.contrib.github import github
from flask_dance.contrib.gitlab import gitlab
from dribdat.sso.auth0 import auth0
from dribdat.sso.mattermost import mattermost
from dribdat.sso.hitobito import hitobito
from dribdat.sso.mattermost import mattermost
# Dribdat modules
from dribdat.user.models import User, Event, Role
from dribdat.extensions import login_manager # noqa: I005
Expand Down Expand Up @@ -76,7 +77,7 @@ def login():


@blueprint.route("/register/", methods=['GET', 'POST'])
async def register():
def register():
"""Register new user."""
if current_app.config['DRIBDAT_NOT_REGISTER']:
flash("Registration currently not possible.", 'warning')
Expand Down Expand Up @@ -113,7 +114,7 @@ async def register():
new_user.active = False
new_user.save()
if current_app.config['MAIL_SERVER']:
await user_activation(current_app, new_user)
user_activation(new_user)
flash("New accounts require activation. "
+ "Please click the dribdat link in your e-mail.", 'success')
else:
Expand Down Expand Up @@ -167,7 +168,7 @@ def forgot():


@blueprint.route("/passwordless/", methods=['POST'])
async def passwordless():
def passwordless():
"""Log in a new user via e-mail."""
if current_app.config['DRIBDAT_NOT_REGISTER'] or \
not current_app.config['MAIL_SERVER']:
Expand All @@ -186,7 +187,7 @@ async def passwordless():
a_user = User.query.filter_by(email=form.email.data).first()
if a_user:
# Continue with reset
await user_activation(current_app, a_user)
user_activation(a_user)
else:
current_app.logger.warn('User not found: %s' % form.email.data)
# Don't let people spy on your address
Expand Down Expand Up @@ -466,7 +467,7 @@ def hitobito_login():
fn = resp_data['first_name'].lower().strip()
ln = resp_data['last_name'].lower().strip()
username = "%s_%s" % (fn, ln)
if username is None:
if username is None or not 'email' in resp_data or not 'id' in resp_data:
flash('Invalid hitobito data format', 'danger')
return redirect(url_for("auth.login", local=1))
return get_or_create_sso_user(
Expand All @@ -475,3 +476,29 @@ def hitobito_login():
resp_data['email'],
)


@blueprint.route("/gitlab_login", methods=["GET", "POST"])
def gitlab_login():
"""Handle login via GitLab."""
if not gitlab.authorized:
flash('Access denied to gitlab', 'danger')
return redirect(url_for("auth.login", local=1))
# Get remote user data
resp = gitlab.get("/api/v4/user")
if not resp.ok:
flash('Unable to access gitlab data', 'danger')
return redirect(url_for("auth.login", local=1))
resp_data = resp.json()
username = None
if 'username' in resp_data and resp_data['username'] is not None:
username = resp_data['username']
elif 'name' in resp_data:
username = resp_data['name']
if username is None or not 'email' in resp_data or not 'id' in resp_data:
flash('Invalid gitlab data format', 'danger')
return redirect(url_for("auth.login", local=1))
return get_or_create_sso_user(
resp_data['id'],
username,
resp_data['email'],
)
20 changes: 19 additions & 1 deletion dribdat/sso/ssoutils.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Helper functions for authentication steps."""
from flask_dance.contrib import (slack, azure, github)
from flask_dance.contrib import (slack, azure, github, gitlab)
from dribdat.sso import (auth0, mattermost, hitobito)


Expand Down Expand Up @@ -38,6 +38,24 @@ def get_auth_blueprint(app):
redirect_to="auth.github_login",
login_url="/login",
)
elif app.config['OAUTH_TYPE'] == 'gitlab':
if app.config['OAUTH_DOMAIN']:
blueprint = gitlab.make_gitlab_blueprint(
client_id=app.config['OAUTH_ID'],
client_secret=app.config['OAUTH_SECRET'],
hostname=app.config['OAUTH_DOMAIN'],
redirect_to="auth.gitlab_login",
login_url="/login",
scope='read_user'
)
else:
blueprint = gitlab.make_gitlab_blueprint(
client_id=app.config['OAUTH_ID'],
client_secret=app.config['OAUTH_SECRET'],
redirect_to="auth.gitlab_login",
login_url="/login",
scope='read_user'
)
elif app.config['OAUTH_TYPE'] == 'auth0':
blueprint = auth0.make_auth0_blueprint(
client_id=app.config['OAUTH_ID'],
Expand Down
88 changes: 45 additions & 43 deletions dribdat/static/css/style.css
Expand Up @@ -1996,62 +1996,64 @@ Data Packages view
SlackIn button
-------------- */

.signin-slack {
border: 0px !important;
background: url('/static/img/sso/slack-sign-in.png') no-repeat;
width: 173px; height: 41px;
}
.signin-azure {
border: 0px !important;
padding-left: 50px; font-size: initial !important;
background: url('/static/img/sso/azure_teams.svg') no-repeat;
width: auto; height: 41px;
}
.signin-github {
border: 0px !important;
background: url('/static/img/sso/github-sign-in.png') no-repeat;
width: 372px; height: 64px;
}
.signin-auth0 {
border: 1px solid #999;
background: url('/static/img/sso/auth0_icon.png') no-repeat;
width: 156px; height: 71px;
}
.signin-mattermost {
border: 1px solid #999;
background: url('/static/img/sso/Mattermost-Logo-Denim.svg') no-repeat;
width: 190px; height: 43px;
}
.signin-hitobito {
border: 1px solid #999;
background: url('/static/img/sso/hitobito.png') no-repeat;
width: 260px; height: 58px;
.sso-login .btn.signin-slack {
background-image: url('/static/img/sso/slack-sign-in.png');
background-position: left; font-size: 0px;
height: 40px;
}
.sso-login .btn.signin-azure {
background-image: url('/static/img/sso/azure_teams.svg');
height: 50px;
}
.sso-login .btn.signin-github {
background-image: url('/static/img/sso/github-sign-in.png');
background-position: left; font-size: 0px;
}
.sso-login .btn.signin-gitlab {
background-image: url('/static/img/sso/gitlab-logo.svg');
}
.sso-login .btn.signin-auth0 {
background-image: url('/static/img/sso/auth0_icon.png');
height: 60px;
}
.sso-login .btn.signin-mattermost {
background-image: url('/static/img/sso/mattermost-icon.png');
height: 60px;
background-position: 16em;
}
.sso-login .btn.signin-hitobito {
background-image: url('/static/img/sso/hitobito.png');
}

.sso-login .btn:hover {
border: 1px solid blue;
color: red;
border-left: 4px double orange;
margin-left: -3px;
}
.sso-login .btn {
font-size: 0px;
height: 50px;
width: 100%;
font-weight: bold;
text-align: left;
background-position: 14em;
background-size: contain;
background-repeat: no-repeat;
}

.account-register a { margin-left: 3.5em; font-size: 125%; }

.account-register::before,
.sso-login::after {
content: 'or';
display: block;
margin: 1em 5em;
margin: 0.5em 5em;
font-size: 200%;
font-family: cursive;
font-style: italic;
}
.sso-login::after {
margin-top: 1em;
margin-bottom: -0.5em;
}

.__slackin { margin-bottom: 10px; display: block; }

/* .signin-slack, .signin-slack:hover {
background-image: url(https://upload.wikimedia.org/wikipedia/commons/7/76/Slack_Icon.png);
background-repeat: no-repeat;
background-size: 24px 24px;
background-position: 10px 8px;
line-height: 41px;
box-shadow: inset 0 0 0 2px #e5e5e5;
padding: 0 42px;
} */
10 changes: 10 additions & 0 deletions dribdat/static/img/sso/gitlab-logo.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dribdat/static/img/sso/mattermost-icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions dribdat/templates/401.html
Expand Up @@ -7,8 +7,12 @@
<div class="jumbotron">
<div class="text-center" style="font-size: 150%">
<h1>✋👮‍</h1>
<p>Please <a href="{{ url_for('auth.login', local=1)}}">log in</a> or
<a href="{{ url_for('auth.register') }}">register</a> to continue.
<p>Please <a href="{{ url_for('auth.login', local=1)}}">log in</a>
{% if not config.DRIBDAT_NOT_REGISTER %}
or
<a href="{{ url_for('auth.register') }}">register</a>
{% endif %}
to continue.
</p>
</div>
</div>
Expand Down