diff --git a/roac/__init__.py b/roac/__init__.py index 0fd48dbe3d9a5a757d2dda483974fb147d8c3f3f..234fb43a675b55edf28738628ea529554f10acc5 100644 --- a/roac/__init__.py +++ b/roac/__init__.py @@ -39,6 +39,12 @@ def create_app(test_config=None): value = ','.join(names) rocketchat_request('POST', '/api/v1/settings/Accounts_BlockedUsernameList', json={'value': value}) + def compute_roles(): + roles = set(app.config['ROCKETCHAT_ROLE_MAP'].get(None, [])) + for group in session['userinfo']['groups']: + roles |= set(app.config['ROCKETCHAT_ROLE_MAP'].get(group, [])) + return list(roles) + @app.cli.command('add-user') @click.argument('loginname') def cli_add_user(loginname): @@ -66,25 +72,52 @@ def create_app(test_config=None): return redirect(url_for('oauth_login')) user = User.query.filter_by(loginname=session['userinfo']['nickname']).one_or_none() if user is None: - return render_template('error_unknown_user.html') + return render_template('unknown_user.html') request.user = user return func(*args, **kwargs) return decorator + csrf_endpoints = [] + + def csrf_protect(func): + urlendpoint = func.__name__ + csrf_endpoints.append(urlendpoint) + @functools.wraps(func) + def decorator(*args, **kwargs): + if '_csrf_token' in request.values: + token = request.values['_csrf_token'] + elif request.get_json() and ('_csrf_token' in request.get_json()): + token = request.get_json()['_csrf_token'] + else: + token = None + if ('_csrf_token' not in session) or (session['_csrf_token'] != token) or not token: + return 'csrf test failed', 403 + return func(*args, **kwargs) + return decorator + + @app.url_defaults + def csrf_inject(endpoint, values): + if endpoint not in csrf_endpoints or not session.get('_csrf_token'): + return + values['_csrf_token'] = session['_csrf_token'] + @app.route('/') @user_required def index(): if request.user.rocketchat_id is None: - return render_template('create.html') - return render_template('sync.html') - - def compute_roles(): - roles = set(app.config['ROCKETCHAT_ROLE_MAP'].get(None, [])) - for group in session['userinfo']['groups']: - roles |= set(app.config['ROCKETCHAT_ROLE_MAP'].get(group, [])) - return list(roles) + return render_template('create.html', roles=compute_roles()) + resp = rocketchat_request('GET', '/api/v1/users.info', params={'userId': request.user.rocketchat_id}) + if not resp.ok or not resp.json().get('success'): + return render_template('error.html') + current_roles = set(resp.json()['user']['roles']) + computed_roles = set(compute_roles()) + new_roles = computed_roles - current_roles + if not new_roles: + return render_template('done.html') + return render_template('update.html', new_roles=list(new_roles)) @app.route('/create', methods=['POST']) + @csrf_protect @user_required def create(): if request.form['password'] != request.form['password2']: @@ -126,6 +159,35 @@ def create_app(test_config=None): flash('Account created!') return redirect(url_for('index')) + @app.route('/update', methods=['POST']) + @csrf_protect + @user_required + def update(): + if request.user.rocketchat_id is None: + return redirect(url_for('index')) + resp = rocketchat_request('GET', '/api/v1/users.info', params={'userId': request.user.rocketchat_id}) + if not resp.ok or not resp.json().get('success'): + flash('Updating roles failed') + return redirect(url_for('index')) + roles = list(set(resp.json()['user']['roles']) | set(compute_roles())) + data = { + 'userId': request.user.rocketchat_id, + 'data': { + 'roles': roles, + } + } + resp = rocketchat_request('POST', '/api/v1/users.update', json=data) + if resp.ok and resp.json()['success']: + flash('Roles updated') + else: + flash('Updating roles failed') + return redirect(url_for('index')) + + @app.route('/logout') + def logout(): + session.clear() + return redirect(app.config['POST_LOGOUT_URL']) + @app.route('/oauth/login') def oauth_login(): client = OAuth2Session(app.config['OAUTH2_CLIENT_ID'], @@ -153,6 +215,7 @@ def create_app(test_config=None): session.clear() session['timestamp'] = datetime.datetime.utcnow().timestamp() session['userinfo'] = userinfo + session['_csrf_token'] = secrets.token_hex(128) return redirect(url_for('index')) os.makedirs(app.instance_path, exist_ok=True) diff --git a/roac/default_config.py b/roac/default_config.py index ae0d37da74dd2421ab7153ebfc1207b4cb8f07a0..940bc97934b7c4109d1b12e9b3aab42995aac58b 100644 --- a/roac/default_config.py +++ b/roac/default_config.py @@ -5,6 +5,8 @@ OAUTH2_USERINFO_URL = 'http://localhost:5000/oauth2/userinfo' OAUTH2_CLIENT_ID = 'roac' OAUTH2_CLIENT_SECRET = 'testsecret' +POST_LOGOUT_URL = 'http://localhost:5000/' + # RocketChat API ROCKETCHAT_BASE_URL = 'https://example.com' ROCKETCHAT_USER_ID = 'USERID' diff --git a/roac/templates/base.html b/roac/templates/base.html index 46b66b83f4dca160485e4f09e1e10042c59e6d68..aa8d432d14f28fd014867455059fbf08ff97f551 100644 --- a/roac/templates/base.html +++ b/roac/templates/base.html @@ -32,7 +32,7 @@ <div class="row justify-content-center"> <div class="col-lg-6 col-md-10" style="background: #f7f7f7; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); padding: 30px;"> <div class="text-center mb-3"> - <img alt="branding logo" src="{{ url_for('static', filename='rocketchat_logo.svg') }}" class="col-lg-8 col-md-12" > + <img alt="branding logo" src="{{ url_for('static', filename='rocketchat_logo.svg') }}" class="col-lg-8 col-md-12" style="max-width: 300px;"> </div> <div class="text-center"> <h2>Single Sign-On Connector</h2> @@ -40,6 +40,9 @@ </div> {% block body %} {% endblock body %} + <div class="text-center"> + <a href="https://legal.cccv.de/">Imprint and Privacy Policy</a> + </div> </div> </div> </main> diff --git a/roac/templates/create.html b/roac/templates/create.html index 453af9626889a279de4feebbd529f8995c39aaf3..f35b308e61c9bc685d7d5b042603d6caa60f550c 100644 --- a/roac/templates/create.html +++ b/roac/templates/create.html @@ -2,7 +2,8 @@ {% block body %} <form class="form" method="POST" action="{{ url_for('create') }}"> <div class="form-group col-12"> - <p>Your CCCV SSO loginname was reserved on chat.rc3.world. You can create an account with your loginname with the form below.</p> + <p>Your user name is blocked on chat.rc3.world, so no one else can create an account with it.</p> + <p>Use the form below to create your account.</p> </div> <div class="form-group col-12"> <label for="username">Username</label> @@ -28,7 +29,21 @@ <input type="password" class="form-control" id="password2" name="password2" required> </div> <div class="form-group col-12"> - <button type="submit" class="btn btn-primary btn-block">Create Account on chat.rc3.world</button> + Roles + <ul> + {% for role in roles|sort %} + <li>{{ role }}</li> + {% endfor %} + </ul> + </div> + <div class="form-group col-12"> + <p>You can find the privacy policy for chat.rc3.world on <a href="https://legal.rc3.world/">https://legal.rc3.world/</a>.</p> + </div> + <div class="form-group col-12"> + <button type="submit" class="btn btn-primary btn-block">Create account on chat.rc3.world</button> + </div> + <div class="form-group col-12"> + <a href="{{ url_for('logout') }}" class="btn btn-secondary btn-block">Logout</a> </div> </form> {% endblock %} diff --git a/roac/templates/done.html b/roac/templates/done.html new file mode 100644 index 0000000000000000000000000000000000000000..bd7a8dd5768c2bd2a09b12af93a586f47aebeac5 --- /dev/null +++ b/roac/templates/done.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} +{% block body %} +<div class="form"> + <div class="form-group col-12"> + <p>You already created an account on chat.rc3.world with your loginname.</p> + <p>Go to <a href="https://chat.rc3.world/">https://chat.rc3.world/</a> to login with your new account!</p> + </div> + <div class="form-group col-12"> + <a href="{{ url_for('logout') }}" class="btn btn-secondary btn-block">Logout</a> + </div> +</div> +{% endblock %} diff --git a/roac/templates/error.html b/roac/templates/error.html new file mode 100644 index 0000000000000000000000000000000000000000..fc7adf387a2c98a452af3e7c80e87e7e7413794b --- /dev/null +++ b/roac/templates/error.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} +{% block body %} +<div class="form"> + <div class="form-group col-12"> + <p>Something went wrong.</p> + <p>Please contact the IT crew via <a href="mailto:it@cccv.de">it@cccv.de</a> or <a href="https://rocket.cccv.de/channel/infra-support">#infra-support on rocket.cccv.de</a>.</p> + </div> + <div class="form-group col-12"> + <a href="{{ url_for('logout') }}" class="btn btn-secondary btn-block">Logout</a> + </div> +</div> +{% endblock %} diff --git a/roac/templates/error_unknown_user.html b/roac/templates/error_unknown_user.html deleted file mode 100644 index 77c327e9c5ea1b2585396dba0f9c62fc78ebd7b9..0000000000000000000000000000000000000000 --- a/roac/templates/error_unknown_user.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'base.html' %} -{% block body %} -<p>Your SSO loginname was not reserved on RocketChat. There is nothing for you to do here.</p> -{% endblock %} diff --git a/roac/templates/sync.html b/roac/templates/sync.html deleted file mode 100644 index f5b6c14d30ae173c08a63d7d9f0ff22b16709271..0000000000000000000000000000000000000000 --- a/roac/templates/sync.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'base.html' %} -{% block body %} -<p>You already created an account on chat.rc3.world with your loginname.</p> -{% endblock %} diff --git a/roac/templates/unknown_user.html b/roac/templates/unknown_user.html new file mode 100644 index 0000000000000000000000000000000000000000..4c1c6527225321e932852508f1998287dd8e0ad8 --- /dev/null +++ b/roac/templates/unknown_user.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% block body %} +<div class="form"> + <div class="form-group col-12"> + <p>Your user name is not reserved on <a href="https://chat.rc3.world/">chat.rc3.world</a>. Go there and use the regular registration form to create your account.</p> + </div> + <div class="form-group col-12"> + <a href="{{ url_for('logout') }}" class="btn btn-secondary btn-block">Logout</a> + </div> +</div> +{% endblock %} diff --git a/roac/templates/update.html b/roac/templates/update.html new file mode 100644 index 0000000000000000000000000000000000000000..c49df85744bf827fc029b6d773354d6e634901a1 --- /dev/null +++ b/roac/templates/update.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% block body %} +<form class="form" method="POST" action="{{ url_for('update') }}"> + <div class="form-group col-12"> + <p>You already created your account on chat.rc3.world, but your roles changed.</p> + <p>New roles: + <ul> + {% for role in new_roles %} + <li>{{ role }}</li> + {% endfor %} + </ul> + </p> + </div> + <div class="form-group col-12"> + <button type="submit" class="btn btn-primary btn-block">Update roles on chat.rc3.world</button> + </div> + <div class="form-group col-12"> + <a href="{{ url_for('logout') }}" class="btn btn-secondary btn-block">Logout</a> + </div> +</form> +{% endblock %}