Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • uffd/uffd
  • rixx/uffd
  • thies/uffd
  • leona/uffd
  • enbewe/uffd
  • strifel/uffd
  • thies/uffd-2
7 results
Show changes
Showing
with 3133 additions and 325 deletions
{% extends 'base.html' %}
{% block body %}
<div class="row">
<form action="{{ url_for('service.oauth2_submit', service_id=service.id, db_id=client.db_id) }}" method="POST" autocomplete="off" class="form col-12 px-0">
<div class="form-group col">
<p class="text-right">
<a href="{{ url_for('service.show', id=service.id) }}" class="btn btn-secondary">{{ _('Cancel') }}</a>
{% if client.db_id %}
<a class="btn btn-danger" href="{{ url_for('service.oauth2_delete', service_id=service.id, db_id=client.db_id) }}" onClick='return confirm({{_("Are you sure?")|tojson}});'>
<i class="fa fa-trash" aria-hidden="true"></i> {{_('Delete')}}
</a>
{% endif %}
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{ _('Save') }}</button>
</p>
</div>
<div class="form-group col">
<label for="client-client-id">{{ _('Client ID') }}</label>
<input type="text" class="form-control" id="client-client-id" name="client_id" value="{{ client.client_id or '' }}" required>
</div>
<div class="form-group col">
<label for="client-client-secret">{{ _('Client Secret') }}</label>
{% if client.db_id %}
<input type="password" autocomplete="new-password" class="form-control" id="client-client-secret" name="client_secret" placeholder="●●●●●●●●">
{% else %}
<input type="password" autocomplete="new-password" class="form-control" id="client-client-secret" name="client_secret" required>
{% endif %}
</div>
<div class="form-group col">
<label for="client-redirect-uris">{{ _('Redirect URIs') }}</label>
<textarea rows="3" class="form-control" id="client-redirect-uris" name="redirect_uris">{{ client.redirect_uris|join('\n') }}</textarea>
<small class="form-text text-muted">
{{ _('One URI per line') }}
</small>
</div>
<div class="form-group col">
<label for="client-logout-uris">{{ _('Logout URIs') }}</label>
<textarea rows="3" class="form-control" id="client-logout-uris" name="logout_uris" placeholder="GET https://example.com/logout">
{%- for logout_uri in client.logout_uris %}
{{ logout_uri.method }} {{ logout_uri.uri }}{{ '\n' if not loop.last }}
{%- endfor %}
</textarea>
<small class="form-text text-muted">
{{ _('One URI per line, prefixed with space-separated method (GET/POST)') }}
</small>
</div>
</form>
</div>
{% endblock %}
...@@ -4,8 +4,19 @@ ...@@ -4,8 +4,19 @@
{% set iconstyle = 'style="width: 1.8em;"'|safe %} {% set iconstyle = 'style="width: 1.8em;"'|safe %}
{% if not user %} {% if not request.user %}
<div class="alert alert-warning" role="alert">Some services may not be publicly listed! Log in to see all services you have access to.</div> <div class="alert alert-warning" role="alert">
<div class="row">
<div class="col-12 col-md-9 col-lg-10 col-xl-10">
{{ _("Some services may not be publicly listed! Log in to see all services you have access to.") }}
</div>
<div class="col-12 col-md-3 col-lg-2 col-xl-2 text-center text-md-right text-lg-right text-xl-right">
<a class="btn btn-primary" href="{{ url_for("session.login", ref=request.full_path) }}">
<i class="fa fa-sign-in-alt" aria-hidden="true"></i> {{ _("Login") }}
</a>
</div>
</div>
</div>
{% endif %} {% endif %}
{% if banner %} {% if banner %}
...@@ -22,7 +33,7 @@ ...@@ -22,7 +33,7 @@
<div class="card-body"> <div class="card-body">
{% if service.logo_url %} {% if service.logo_url %}
{% if service.url and service.has_access %}<a href="{{ service.url }}" class="text-reset">{% endif %} {% if service.url and service.has_access %}<a href="{{ service.url }}" class="text-reset">{% endif %}
<img alt="Logo for {{ service.title }}" src="{{ service.logo_url }}" style="width: 100%; height: 10em; object-fit: contain; {{ 'filter: grayscale(100%);' if not service.has_access }}"> <img alt="{{ _("Logo for %(service_title)s", service_title=service.title) }}" src="{{ service.logo_url }}" style="width: 100%; height: 10em; object-fit: contain; {{ 'filter: grayscale(100%);' if not service.has_access }}">
{% if service.url and service.has_access %}</a>{% endif %} {% if service.url and service.has_access %}</a>{% endif %}
{% endif %} {% endif %}
<h5 class="card-title"> <h5 class="card-title">
...@@ -41,7 +52,7 @@ ...@@ -41,7 +52,7 @@
</div> </div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
{% if not service.has_access %} {% if not service.has_access %}
<div class="list-group-item"><i class="fas fa-shield-alt" {{ iconstyle }}></i> No access</div> <div class="list-group-item"><i class="fas fa-shield-alt" {{ iconstyle }}></i> {{_("No access")}}</div>
{% elif service.permission %} {% elif service.permission %}
<div class="list-group-item"><i class="fas fa-shield-alt" {{ iconstyle }}></i> {{ service.permission }}</div> <div class="list-group-item"><i class="fas fa-shield-alt" {{ iconstyle }}></i> {{ service.permission }}</div>
{% endif %} {% endif %}
...@@ -59,6 +70,12 @@ ...@@ -59,6 +70,12 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% if request.user and request.user.is_in_group(config['ACL_ADMIN_GROUP']) %}
<div class="text-right mt-2">
<a href="{{ url_for('service.index') }}" class="btn btn-primary">{{ _('Manage OAuth2 and API clients') }}</a>
</div>
{% endif %}
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 mt-2"> <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 mt-2">
{% for service in services if service.has_access %} {% for service in services if service.has_access %}
{{ service_card(service) }} {{ service_card(service) }}
...@@ -75,7 +92,7 @@ ...@@ -75,7 +92,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">{{ info.title }}</h5> <h5 class="modal-title">{{ info.title }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="{{_("Close")}}">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
......
{% extends 'base.html' %}
{% block body %}
<div class="row">
<form action="{{ url_for('service.edit_submit', id=service.id) }}" method="POST" autocomplete="off" class="form col-12 px-0">
<div class="form-group col">
<p class="text-right">
<a href="{{ url_for('service.index') }}" class="btn btn-secondary">{{ _('Cancel') }}</a>
{% if service.id %}
<a class="btn btn-danger" href="{{ url_for('service.delete', id=service.id) }}" onClick='return confirm({{_("Are you sure?")|tojson}});'>
<i class="fa fa-trash" aria-hidden="true"></i> {{_('Delete')}}
</a>
{% endif %}
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{ _('Save') }}</button>
</p>
</div>
<div class="form-group col">
<label for="service-name">{{ _('Name') }}</label>
<input type="text" class="form-control" id="service-name" name="name" value="{{ service.name or '' }}" required>
</div>
<div class="form-group col">
<label for="access-group">{{ _('Access Restriction') }}</label>
<select class="form-control" id="access-group" name="access-group">
<option value="" class="text-muted">{{ _('No user has access') }}</option>
<option value="all" class="text-muted" {{ 'selected' if not service.limit_access }}>{{ _('All users have access (legacy)') }}</option>
{% for group in all_groups %}
<option value="{{ group.id }}" {{ 'selected' if group == service.access_group and service.limit_access }}>{{ _('Members of group "%(group_name)s" have access', group_name=group.name) }}</option>
{% endfor %}
</select>
</div>
<div class="form-group col">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="hide-deactivated-users" name="hide_deactivated_users" value="1" aria-label="enabled" {{ 'checked' if service.hide_deactivated_users }}>
<label class="form-check-label" for="hide-deactivated-users">{{ _('Hide deactivated users from service') }}</label>
</div>
</div>
<div class="form-group col">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="service-enable-email-preferences" name="enable_email_preferences" value="1" aria-label="enabled" {{ 'checked' if service.enable_email_preferences }}>
<label class="form-check-label" for="service-enable-email-preferences">{{ _('Allow users with access to select a different e-mail address for this service') }}</label>
<small class="form-text text-muted">
{{ _('If disabled, the service always uses the primary e-mail address.') }}
</small>
</div>
</div>
<div class="form-group col">
<label for="remailer-mode">
{{ _('Hide e-mail addresses with remailer') }}
{% if not remailer.configured %}
<i class="fas fa-exclamation-triangle text-warning" data-toggle="tooltip" data-placement="top" title="{{ _('This option has no effect: Remailer config options are unset') }}"></i>
{% endif %}
</label>
<select class="form-control" id="remailer-mode" name="remailer-mode">
<option value="{{ RemailerMode.DISABLED.name }}" {{ 'selected' if service.remailer_mode == RemailerMode.DISABLED }}>
{{ _('Remailer disabled') }}
</option>
<option value="{{ RemailerMode.ENABLED_V2.name }}" {{ 'selected' if service.remailer_mode == RemailerMode.ENABLED_V2 }}>
{{ _('Remailer enabled') }}
</option>
<option value="{{ RemailerMode.ENABLED_V1.name }}" {{ 'selected' if service.remailer_mode == RemailerMode.ENABLED_V1 }}>
{{ _('Remailer enabled (deprecated, case-sensitive format)') }}
</option>
</select>
<small class="form-text text-muted">
{{ _('Some services notify users about changes to their e-mail address. Modifying this setting immediatly affects the e-mail addresses of all users and can cause masses of notification e-mails.') }}
</small>
</div>
<div class="form-group col">
<p class="mb-2">
{{ _('Overwrite remailer setting for specific users') }}
</p>
<div class="input-group" id="remailer-mode-overwrite">
<input class="form-control" name="remailer-overwrite-users" placeholder="{{ _('Login names') }}" value="{{ remailer_overwrites|map(attribute='user')|map(attribute='loginname')|sort|join(', ') }}">
<select class="form-control" name="remailer-overwrite-mode">
{% set remailer_overwrite_mode = remailer_overwrites|map(attribute='remailer_overwrite_mode')|first or RemailerMode.ENABLED_V2 %}
<option value="{{ RemailerMode.DISABLED.name }}" {{ 'selected' if remailer_overwrite_mode == RemailerMode.DISABLED }}>
{{ _('Remailer disabled') }}
</option>
<option value="{{ RemailerMode.ENABLED_V2.name }}" {{ 'selected' if remailer_overwrite_mode == RemailerMode.ENABLED_V2 }}>
{{ _('Remailer enabled') }}
</option>
<option value="{{ RemailerMode.ENABLED_V1.name }}" {{ 'selected' if remailer_overwrite_mode == RemailerMode.ENABLED_V1 }}>
{{ _('Remailer enabled (deprecated, case-sensitive format)') }}
</option>
</select>
</div>
<small class="form-text text-muted">
{{ _('Useful for testing remailer before enabling it for all users. Specify users as a comma-seperated list of login names.') }}
</small>
</div>
</form>
{% if service.id %}
<div class="col-12">
<hr>
<h5>OAuth2 Clients</h5>
<p class="text-right">
<a class="btn btn-primary" href="{{ url_for('service.oauth2_show', service_id=service.id) }}">
<i class="fa fa-plus" aria-hidden="true"></i> {{_("New")}}
</a>
</p>
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">{{ _('Client ID') }}</th>
</tr>
</thead>
<tbody>
{% for client in service.oauth2_clients|sort(attribute='client_id') %}
<tr>
<td>
<a href="{{ url_for("service.oauth2_show", service_id=service.id, db_id=client.db_id) }}">
{{ client.client_id }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="col-12">
<hr>
<h5>API Clients</h5>
<p class="text-right">
<a class="btn btn-primary" href="{{ url_for('service.api_show', service_id=service.id) }}">
<i class="fa fa-plus" aria-hidden="true"></i> {{_("New")}}
</a>
</p>
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">{{ _('Name') }}</th>
<th scope="col">{{ _('Permissions') }}</th>
</tr>
</thead>
<tbody>
{% for client in service.api_clients|sort(attribute='auth_username') %}
<tr>
<td>
<a href="{{ url_for("service.api_show", service_id=service.id, id=client.id) }}">
{{ client.auth_username }}
</a>
</td>
<td>
{% for perm in ['users', 'checkpassword', 'mail_aliases', 'remailer'] if client.has_permission(perm) %}
{{ perm }}{{ ',' if not loop.last }}
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
{% endblock %}
{% extends 'base_narrow.html' %}
{% block body %}
{% if not initiation %}
<form action="{{ url_for("session.deviceauth") }}" autocomplete="off">
{% elif not confirmation %}
<form action="{{ url_for("session.deviceauth_submit") }}" method="POST" autocomplete="off">
{% else %}
<form action="{{ url_for("session.deviceauth_finish") }}" method="POST" autocomplete="off">
{% endif %}
<div class="col-12">
<h2 class="text-center">{{_('Authorize Device Login')}}</h2>
</div>
<div class="form-group col-12">
<p>{{_('Log into a service on another device without entering your password.')}}</p>
</div>
<div class="form-group col-12">
<label for="initiation-code">{{_('Initiation Code')}}</label>
{% if not initiation %}
<input type="text" class="form-control" id="initiation-code" name="initiation-code" value="{{ initiation_code or '' }}" required="required" tabindex = "1" autofocus>
{% else %}
<input type="text" class="form-control" id="initiation-code" name="initiation-code" value="{{ initiation.code }}" readonly>
{% endif %}
</div>
{% if confirmation %}
<div class="form-group col-12">
<label for="confirmation-code">{{_('Confirmation Code')}}</label>
<input type="text" class="form-control" id="confirmation-code" name="confirmation-code" value="{{ confirmation.code }}" readonly>
</div>
{% endif %}
{% if not initiation %}
<div class="form-group col-12">
<p>{{_('Start logging into a service on the other device and chose "Device Login" on the login page. Enter the displayed initiation code in the box above.')}}</p>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block" tabindex = "2">{{_('Continue')}}</button>
</div>
<div class="form-group col-12">
<a href="{{ url_for('index') }}" class="btn btn-secondary btn-block" tabindex="0">{{_('Cancel')}}</a>
</div>
{% elif not confirmation %}
<div class="form-group col-12">
<p>{{_('Authorize the login for service <b>%(service_name)s</b>?', service_name=initiation.description|e)|safe}}</p>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block" tabindex = "2">{{_('Authorize Login')}}</button>
</div>
<div class="form-group col-12">
<a href="{{ url_for('index') }}" class="btn btn-secondary btn-block" tabindex="0">{{_('Cancel')}}</a>
</div>
{% else %}
<div class="form-group col-12">
<p>{{_('Enter the confirmation code on the other device and complete the login. Click <em>Finish</em> afterwards.')|safe}}</p>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block" tabindex = "2">{{_('Finish')}}</button>
</div>
{% endif %}
</form>
{% endblock %}
{% extends 'base_narrow.html' %}
{% block body %}
<form action="{{ url_for("session.devicelogin_submit", ref=ref) }}" method="POST" autocomplete="off">
<div class="col-12">
<h2 class="text-center">{{_('Device Login')}}</h2>
</div>
<div class="form-group col-12">
<p>{{_('Use a login session on another device (e.g. your laptop) to log into a service without entering your password.')}}</p>
</div>
{% if initiation %}
<div class="form-group col-12">
<label for="initiation-code">{{_('Initiation Code')}}</label>
<input type="text" class="form-control" id="initiation-code" name="initiation-code" value="{{ initiation.code }}" readonly>
</div>
<input type="hidden" class="form-control" id="initiation-secret" name="initiation-secret" value="{{ initiation.secret }}">
<div class="form-group col-12">
<label for="confirmation-code">{{_('Confirmation Code')}}</label>
<input type="text" class="form-control" id="confirmation-code" name="confirmation-code" required="required" tabindex = "1" autofocus>
</div>
<div class="form-group col-12">
<p>{{_('Open <code><a href="%(deviceauth_url)s">%(deviceauth_url)s</a></code> on the other device and enter the initiation code there. Then enter the confirmation code in the box above.', deviceauth_url=url_for('session.deviceauth', _external=True)|e)|safe}}</p>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block" tabindex = "3">{{_('Continue')}}</button>
</div>
{% endif %}
<div class="form-group col-12">
<a href="{{ url_for('session.login', ref=ref, devicelogin=True) }}" class="btn btn-secondary btn-block" tabindex="0">{{_('Cancel')}}</a>
</div>
</form>
{% endblock %}
{% extends 'base_narrow.html' %}
{% block body %}
<form action="{{ url_for("session.login", ref=ref) }}" method="POST">
<div class="col-12">
<h2 class="text-center">{{_("Login")}}</h2>
</div>
{% if config['LOGIN_BANNER'] %}
<p>{{ config['LOGIN_BANNER'] }}</p>
{% endif %}
<div class="form-group col-12">
<label for="user-loginname">{{_("Login Name")}}</label>
<input type="text" autocomplete="username" class="form-control" id="user-loginname" name="loginname" required="required" tabindex="1" autofocus>
</div>
<div class="form-group col-12">
<label for="user-password1">{{_("Password")}}</label>
<input type="password" autocomplete="current-password" class="form-control" id="user-password1" name="password" required="required" tabindex="2">
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block" tabindex="3">{{_("Login")}}</button>
</div>
{% if request.values.get('devicelogin') %}
<div class="text-center text-muted mb-3">{{_("- or -")}}</div>
<div class="form-group col-12">
<a href="{{ url_for('session.devicelogin_start', ref=ref) }}" class="btn btn-primary btn-block" tabindex="0">{{_("Login with another device")}}</a>
</div>
{% endif %}
<div class="clearfix col-12">
{% if config['SELF_SIGNUP'] %}
<a href="{{ url_for("signup.signup_start") }}" class="float-left">{{_("Register")}}</a>
{% endif %}
<a href="{{ url_for("selfservice.forgot_password") }}" class="float-right">{{_("Forgot Password?")}}</a>
</div>
</form>
{% endblock %}
{% extends 'base.html' %} {% extends 'base_narrow.html' %}
{% block body %} {% block body %}
<form action="{{ url_for("session.mfa_auth_finish", ref=ref) }}" method="POST" autocomplete="off">
<form action="{{ url_for("mfa.auth_finish", ref=ref) }}" method="POST"> <div class="col-12 mb-3">
<div class="row mt-2 justify-content-center"> <h2 class="text-center">{{_("Two-Factor Authentication")}}</h2>
<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>
<div class="text-center"> {% if request.user_pre_mfa.mfa_webauthn_methods %}
<img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > <noscript>
</div>
<div class="col-12 mb-3">
<h2 class="text-center">Two-Factor Authentication</h2>
</div>
{% if webauthn_methods %}
<noscript>
<div class="form-group col-12">
<div id="webauthn-nojs" class="alert alert-warning" role="alert">Enable javascript for authentication with U2F/FIDO2 devices</div>
</div>
</noscript>
<div id="webauthn-unsupported" class="form-group col-12 d-none">
<div class="alert alert-warning" role="alert">Authentication with U2F/FIDO2 devices is not supported by your browser</div>
</div>
<div class="form-group col-12 d-none webauthn-group">
<div id="webauthn-alert" class="alert alert-warning d-none" role="alert"></div>
<button type="button" id="webauthn-btn" class="btn btn-primary btn-block">
<span id="webauthn-spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
<span id="webauthn-btn-text">Authenticate with U2F/FIDO2 device</span>
</button>
</div>
<div class="text-center text-muted d-none webauthn-group mb-3">- or -</div>
{% endif %}
<div class="form-group col-12 mb-2">
<input type="text" class="form-control" id="mfa-code" name="code" required="required" placeholder="Code from your authenticator app or recovery code" autocomplete="off" autofocus>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block">Verify</button>
</div>
<div class="form-group col-12"> <div class="form-group col-12">
<a href="{{ url_for("session.logout") }}" class="btn btn-secondary btn-block">Cancel</a> <div id="webauthn-nojs" class="alert alert-warning" role="alert">{{_("Enable javascript for authentication with U2F/FIDO2 devices")}}</div>
</div> </div>
</noscript>
<div id="webauthn-unsupported" class="form-group col-12 d-none">
<div class="alert alert-warning" role="alert">{{_("Authentication with U2F/FIDO2 devices is not supported by your browser")}}</div>
</div>
<div class="form-group col-12 d-none webauthn-group">
<div id="webauthn-alert" class="alert alert-warning d-none" role="alert"></div>
<button type="button" id="webauthn-btn" class="btn btn-primary btn-block">
<span id="webauthn-spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
<span id="webauthn-btn-text">{{_("Authenticate with U2F/FIDO2 device")}}</span>
</button>
</div>
<div class="text-center text-muted d-none webauthn-group mb-3">- {{_("or")}} -</div>
{% endif %}
<div class="form-group col-12 mb-2">
<input type="text" class="form-control" id="mfa-code" name="code" required="required" placeholder="{{_("Code from your authenticator app or recovery code")}}" autofocus>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block">{{_("Verify")}}</button>
</div>
<div class="form-group col-12">
<a href="{{ url_for("session.logout") }}" class="btn btn-secondary btn-block">{{_("Cancel")}}</a>
</div> </div>
</div>
</form> </form>
{% if webauthn_supported and webauthn_methods %} {% if webauthn_supported and request.user_pre_mfa.mfa_webauthn_methods %}
<script src="{{ url_for('static', filename="cbor.js") }}"></script> <script src="{{ url_for('static', filename="cbor.js") }}"></script>
<script> <script>
function begin_webauthn() { function begin_webauthn() {
$('#webauthn-alert').addClass('d-none'); $('#webauthn-alert').addClass('d-none');
$('#webauthn-spinner').removeClass('d-none'); $('#webauthn-spinner').removeClass('d-none');
$('#webauthn-btn-text').text('Fetching credential data'); $('#webauthn-btn-text').text({{ _('Contacting server')|tojson }});
$('#webauthn-btn').prop('disabled', true); $('#webauthn-btn').prop('disabled', true);
fetch({{ url_for('mfa.auth_webauthn_begin')|tojson }}, { fetch({{ url_for('session.mfa_auth_webauthn_begin')|tojson }}, {
method: 'POST', method: 'POST',
}).then(function(response) { }).then(function(response) {
if (response.ok) { if (response.ok) {
return response.arrayBuffer(); return response.arrayBuffer();
} else if (response.status == 403) { } else if (response.status == 403) {
window.location = {{ request.url|tojson }}; /* reload */ window.location = {{ request.url|tojson }}; /* reload */
throw new Error('Session timed out'); throw new Error({{ _('Session timed out')|tojson }});
} else if (response.status == 404) { } else if (response.status == 404) {
throw new Error('You have not registered any U2F/FIDO2 devices for your account'); throw new Error({{ _('You have not registered any U2F/FIDO2 devices for your account')|tojson }});
} else { } else {
throw new Error('Server error'); throw new Error({{ _('Server error')|tojson }});
} }
}).then(CBOR.decode).then(function(options) { }).then(CBOR.decode).then(function(options) {
$('#webauthn-btn-text').text('Waiting for response from your device'); $('#webauthn-btn-text').text({{ _('Waiting for device')|tojson }});
return navigator.credentials.get(options); return navigator.credentials.get(options);
}).then(function(assertion) { }).then(function(assertion) {
$('#webauthn-btn-text').text('Verifing response'); $('#webauthn-btn-text').text({{ _('Verifing response')|tojson }});
return fetch({{ url_for('mfa.auth_webauthn_complete')|tojson }}, { return fetch({{ url_for('session.mfa_auth_webauthn_complete')|tojson }}, {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/cbor'}, headers: {'Content-Type': 'application/cbor'},
body: CBOR.encode({ body: CBOR.encode({
...@@ -81,37 +73,37 @@ function begin_webauthn() { ...@@ -81,37 +73,37 @@ function begin_webauthn() {
}).then(function(response) { }).then(function(response) {
if (response.ok) { if (response.ok) {
$('#webauthn-spinner').addClass('d-none'); $('#webauthn-spinner').addClass('d-none');
$('#webauthn-btn-text').text('Success, redirecting'); $('#webauthn-btn-text').text({{ _('Success, redirecting')|tojson }});
window.location = {{ (ref or url_for('index'))|tojson }}; window.location = {{ (ref or url_for('index'))|tojson }};
} else if (response.status == 403) { } else if (response.status == 403) {
window.location = {{ request.url|tojson }}; /* reload */ window.location = {{ request.url|tojson }}; /* reload */
throw new Error('Session timed out'); throw new Error({{ _('Session timed out')|tojson }});
} else { } else {
throw new Error('Response from authenticator rejected'); throw new Error({{ _('Invalid response from device')|tojson }});
} }
}).catch(function(err) { }).catch(function(err) {
console.log(err); console.log(err);
/* various webauthn errors */ /* various webauthn errors */
if (err.name == 'NotAllowedError') if (err.name == 'NotAllowedError')
$('#webauthn-alert').text('Authentication timed out, was aborted or not allowed'); $('#webauthn-alert').text({{ _('Authentication timed out, was aborted or not allowed')|tojson }});
else if (err.name == 'InvalidStateError') else if (err.name == 'InvalidStateError')
$('#webauthn-alert').text('Device is not registered for your account'); $('#webauthn-alert').text({{ _('Device is not registered for your account')|tojson }});
else if (err.name == 'AbortError') else if (err.name == 'AbortError')
$('#webauthn-alert').text('Authentication was aborted'); $('#webauthn-alert').text({{ _('Authentication was aborted')|tojson }});
else if (err.name == 'NotSupportedError') else if (err.name == 'NotSupportedError')
$('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser'); $('#webauthn-alert').text({{ _('U2F and FIDO2 devices are not supported by your browser')|tojson }});
/* errors from fetch() */ /* errors from fetch() */
else if (err.name == 'TypeError') else if (err.name == 'TypeError')
$('#webauthn-alert').text('Could not connect to server'); $('#webauthn-alert').text({{ _('Could not connect to server')|tojson }});
/* our own errors */ /* our own errors */
else if (err.name == 'Error') else if (err.name == 'Error')
$('#webauthn-alert').text(err.message); $('#webauthn-alert').text(err.message);
/* fallback */ /* fallback */
else else
$('#webauthn-alert').text('Authentication failed ('+err+')'); $('#webauthn-alert').text({{ _('Authentication failed ')|tojson }}+'('+err+')');
$('#webauthn-alert').removeClass('d-none'); $('#webauthn-alert').removeClass('d-none');
$('#webauthn-spinner').addClass('d-none'); $('#webauthn-spinner').addClass('d-none');
$('#webauthn-btn-text').text('Try FIDO token again'); $('#webauthn-btn-text').text({{ _('Retry authenticate with U2F/FIDO2 device')|tojson }});
$('#webauthn-btn').prop('disabled', false); $('#webauthn-btn').prop('disabled', false);
}); });
} }
......
{% extends 'base_narrow.html' %}
{% block body %}
<form action="{{ url_for(".signup_confirm_submit", signup_id=signup.id, token=signup.token) }}" method="POST">
<div class="col-12">
<h2 class="text-center">{{_('Complete Registration')}}</h2>
</div>
<div class="form-group col-12">
<label for="user-password1">{{_('Please enter your password to complete the account registration')}}</label>
<input type="password" autocomplete="current-password" class="form-control" id="user-password1" name="password" required="required">
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block">{{_('Complete Account Registration')}}</button>
</div>
</form>
{% endblock %}
Hi {{ signup.displayname }},
an account was created on the {{ config.ORGANISATION_NAME }} infrastructure with this mail address.
Please visit the following url to complete the account registration:
{{ url_for('signup.signup_confirm', signup_id=signup.id, token=signup.token, _external=True) }}
**The link is valid for 48h**
{% if config.WELCOME_TEXT %}
{{ config.WELCOME_TEXT }}
{% endif -%}
If you have not requested an account on the {{ config.ORGANISATION_NAME }} infrastructure, you can ignore this mail.
{% extends 'base.html' %} {% extends 'base_narrow.html' %}
{% block body %} {% block body %}
<form action="{{ url_for('.signup_submit') }}" method="POST" onInput="password2.setCustomValidity(password1.value != password2.value ? 'Passwords do not match.' : '') "> <form action="{{ url_for('.signup_submit') }}" method="POST" onInput="password2.setCustomValidity(password1.value != password2.value ? 'Passwords do not match.' : '') ">
<div class="row mt-2 justify-content-center"> <div class="col-12">
<div class="col-lg-6 col-md-10" style="background: #f7f7f7; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); padding: 30px;"> <h2 class="text-center">{{_('Account Registration')}}</h2>
<div class="text-center"> </div>
<img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > <div class="form-group col-12">
</div> <label for="user-loginname">{{_('Login Name')}}</label>
<div class="col-12"> <div class="js-only-input-group">
<h2 class="text-center">Account Registration</h2> <input type="text" autocomplete="username" class="form-control" id="user-loginname" name="loginname" aria-describedby="loginname-feedback" value="{{ request.form.loginname }}" minlength=1 maxlength=32 pattern="[a-z0-9_-]*" required>
</div> <div class="js-only-input-group-append d-none">
{% if error %} <button class="btn btn-outline-secondary rounded-right" type="button" id="check-loginname">{{_('Check')}}</button>
<div class="form-group col-12">
<div class="alert alert-danger" role="alert">{{ error }}</div>
</div>
{% endif %}
<div class="form-group col-12">
<label for="user-loginname">Login Name</label>
<div class="js-only-input-group">
<input type="text" class="form-control" id="user-loginname" name="loginname" aria-describedby="loginname-feedback" value="{{ request.form.loginname }}" minlength=1 maxlength=32 pattern="[a-z0-9_-]*" required>
<div class="js-only-input-group-append d-none">
<button class="btn btn-outline-secondary rounded-right" type="button" id="check-loginname">Check</button>
</div>
<div id="loginname-feedback" class="invalid-feedback">foobar</div>
</div> </div>
<small class="form-text text-muted"> <div id="loginname-feedback" class="invalid-feedback"></div>
At least one and at most 32 lower-case characters, digits, dashes ("-") or underscores ("_"). <b>Cannot be changed later!</b>
</small>
</div>
<div class="form-group col-12">
<label for="user-displayname">Display Name</label>
<input type="text" class="form-control" id="user-displayname" name="displayname" value="{{ request.form.displayname }}" minlength=1 maxlength=128 required>
<small class="form-text text-muted">
At least one and at most 128 characters, no other special requirements.
</small>
</div>
<div class="form-group col-12">
<label for="user-mail">E-Mail Address</label>
<input type="email" class="form-control" id="user-mail" name="mail" value="{{ request.form.mail }}" required>
<small class="form-text text-muted">
We will send a confirmation mail to this address that you need to complete the registration.
</small>
</div>
<div class="form-group col-12">
<label for="user-password1">Password</label>
<input type="password" class="form-control" id="user-password1" name="password1" minlength=8 maxlength=256 required>
<small class="form-text text-muted">
At least 8 and at most 256 characters, no other special requirements. But please don't be stupid, do use a password manager.
</small>
</div>
<div class="form-group col-12">
<label for="user-password2">Repeat Password</label>
<input type="password" class="form-control" id="user-password2" name="password2" required>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block">Create Account</button>
</div> </div>
<small class="form-text text-muted">
{{_('At least one and at most 32 lower-case characters, digits, dashes ("-") or underscores ("_"). <b>Cannot be changed later!</b>')|safe}}
</small>
</div>
<div class="form-group col-12">
<label for="user-displayname">{{_('Display Name')}}</label>
<input type="text" autocomplete="nickname" class="form-control" id="user-displayname" name="displayname" value="{{ request.form.displayname }}" minlength=1 maxlength=128 required>
<small class="form-text text-muted">
{{_('At least one and at most 128 characters, no other special requirements.')}}
</small>
</div>
<div class="form-group col-12">
<label for="user-mail">{{_('E-Mail Address')}}</label>
<input type="email" autocomplete="email" class="form-control" id="user-mail" name="mail" value="{{ request.form.mail }}" required>
<small class="form-text text-muted">
{{_('We will send a confirmation mail to this address that you need to complete the registration.')}}
</small>
</div>
<div class="form-group col-12">
<label for="user-password1">{{_('Password')}}</label>
<input type="password" autocomplete="new-password" class="form-control" id="user-password1" name="password1" minlength={{ User.PASSWORD_MINLEN }} maxlength={{ User.PASSWORD_MAXLEN }} pattern="{{ User.PASSWORD_REGEX }}" required>
<small class="form-text text-muted">
{{ User.PASSWORD_DESCRIPTION|safe }}
</small>
</div>
<div class="form-group col-12">
<label for="user-password2">{{_('Repeat Password')}}</label>
<input type="password" autocomplete="new-password" class="form-control" id="user-password2" name="password2" required>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block">{{_('Create Account')}}</button>
</div> </div>
</div>
</form> </form>
<script> <script>
...@@ -83,13 +71,13 @@ $("#check-loginname").on("click", function () { ...@@ -83,13 +71,13 @@ $("#check-loginname").on("click", function () {
$("#user-loginname").addClass("is-valid"); $("#user-loginname").addClass("is-valid");
$("#loginname-feedback").text(""); $("#loginname-feedback").text("");
} else if (resp.status == 'exists') { } else if (resp.status == 'exists') {
$("#loginname-feedback").text("The name is already taken"); $("#loginname-feedback").text({{_("The name is already taken")|tojson}});
$("#user-loginname").addClass("is-invalid"); $("#user-loginname").addClass("is-invalid");
} else if (resp.status == 'ratelimited') { } else if (resp.status == 'ratelimited') {
$("#loginname-feedback").text("Too many requests! Please wait a bit before trying again!"); $("#loginname-feedback").text({{_("Too many requests! Please wait a bit before trying again!")|tojson}});
$("#user-loginname").addClass("is-invalid"); $("#user-loginname").addClass("is-invalid");
} else { } else {
$("#loginname-feedback").text("The name is invalid"); $("#loginname-feedback").text({{_("The name is invalid")|tojson}});
$("#user-loginname").addClass("is-invalid"); $("#user-loginname").addClass("is-invalid");
} }
} }
......
{% extends 'base_narrow.html' %}
{% block body %}
<div class="col-12 mb-3">
<h2 class="text-center">{{_('Confirm your E-Mail Address')}}</h2>
</div>
<p>{{_('We sent a confirmation mail to <b>%(signup_mail)s</b>. You need to confirm your mail address within 48 hours to complete the account registration.', signup_mail=signup.mail|e)|safe}}</p>
<p>{{_("If you mistyped your mail address or don't receive the confirmation mail for another reason, retry the registration procedure from the beginning.")}}</p>
{% endblock %}
...@@ -3,44 +3,47 @@ ...@@ -3,44 +3,47 @@
{% block body %} {% block body %}
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<p class="text-right">
<a class="btn btn-primary" href="{{ url_for("user.show") }}">
<i class="fa fa-plus" aria-hidden="true"></i> {{_("New")}}
</a>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#csvimport">
<i class="fa fa-file-csv" aria-hidden="true"></i> {{_("CSV import")}}
</button>
</p>
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
<tr> <tr>
<th scope="col">uid</th> <th scope="col">{{_("UID")}}</th>
<th scope="col">login name</th> <th scope="col">{{_("Login Name")}}</th>
<th scope="col">display name</th> <th scope="col">{{_("Display Name")}}</th>
<th scope="col"> <th scope="col">{{_("Roles")}}</th>
<p class="text-right">
<a class="btn btn-primary" href="{{ url_for("user.show") }}">
<i class="fa fa-plus" aria-hidden="true"></i> New
</a>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#csvimport">
<i class="fa fa-file-csv" aria-hidden="true"></i> CSV import
</button>
</p>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user in users|sort(attribute="uid") %} {% for user in users|sort(attribute="unix_uid") %}
<tr id="user-{{ user.uid }}"> <tr id="user-{{ user.id }}">
<th scope="row"> <th scope="row">
{{ user.uid }} {{ user.unix_uid }}
</th> </th>
<td> <td>
<a href="{{ url_for("user.show", uid=user.uid) }}"> <a href="{{ url_for("user.show", id=user.id) }}">
{{ user.loginname }} {{ user.loginname }}
</a> </a>
{% if user.is_service_user %}
<span class="badge badge-secondary">{{_('service')}}</span>
{% endif %}
{% if user.is_deactivated %}
<span class="badge badge-danger">{{ _('deactivated') }}</span>
{% endif %}
</td> </td>
<td> <td>
{{ user.displayname }} {{ user.displayname }}
</td> </td>
<td> <td>
<p class="text-right"> {% for role in user.roles|sort(attribute="name") %}
<a href="{{ url_for("user.show", uid=user.uid) }}" class="btn btn-primary"> <a href="{{ url_for("role.show", roleid=role.id) }}">{{ role.name }}</a>{% if not loop.last %}, {% endif %}
<i class="fa fa-edit" aria-hidden="true"></i> Edit {% endfor %}
</a>
</p>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -54,16 +57,14 @@ ...@@ -54,16 +57,14 @@
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Import a csv formated list of users</h5> <h5 class="modal-title" id="exampleModalLabel">{{_("Import a csv formated list of users")}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="{{_('Close')}}">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p> <p>
The format should be "loginname,mailaddres,roleid1;roleid2". {{_('The format should be "loginname,mailaddres,roleid1;roleid2". Neither setting the display name nor setting passwords is supported (yet). Example:')}}
Neither setting the display name nor setting passwords is supported (yet).
Example:
</p> </p>
<pre> <pre>
testuser1,foobar@example.com,5;2;6 testuser1,foobar@example.com,5;2;6
...@@ -72,10 +73,14 @@ testuser5,foobadfar@example.com,0;5;2 ...@@ -72,10 +73,14 @@ testuser5,foobadfar@example.com,0;5;2
testuser2,foobaadsfr@example.com,5;2 testuser2,foobaadsfr@example.com,5;2
</pre> </pre>
<textarea rows="10" class="form-control" name="csv"></textarea> <textarea rows="10" class="form-control" name="csv"></textarea>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="ignore-loginname-blocklist" name="ignore-loginname-blocklist" value="1" aria-label="enabled">
<label class="form-check-label" for="ignore-loginname-blocklist">{{_("Ignore login name blocklist")}}</label>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">{{_("Close")}}</button>
<button type="submit" class="btn btn-primary">Import</button> <button type="submit" class="btn btn-primary">{{_("Import")}}</button>
</div> </div>
</div> </div>
</div> </div>
......
{% extends 'base.html' %}
{% macro new_email_row(tmp_id) %}
<tr>
<td>
<input class="form-control form-control-sm" type="email" name="newemail-{{ tmp_id }}-address" placeholder="{{ _('New address') }}">
</td>
<td class="text-center">
<input type="checkbox" value="1" name="newemail-{{ tmp_id }}-verified">
</td>
<td class="text-center">
<button type="button" class="btn btn-sm delete-new-email-row d-none"><i class="fas fa-times"></i></button>
</td>
</tr>
{% endmacro %}
{% block body %}
{% if user.id %}
<form action="{{ url_for("user.update", id=user.id) }}" method="POST" autocomplete="off">
{% else %}
<form action="{{ url_for("user.create") }}" method="POST" autocomplete="off">
{% endif %}
<div class="align-self-center">
{% if user.id and user.is_deactivated %}
<div class="alert alert-warning">
{{ _('This account is deactivated. The user cannot login and existing sessions are not usable. The user cannot log into services, but existing sessions on services might still be active.') }}
</div>
{% endif %}
<div class="clearfix pb-2"><div class="float-sm-right">
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_("Save")}}</button>
<a href="{{ url_for("user.index") }}" class="btn btn-secondary">{{_("Cancel")}}</a>
{% if user.id and not user.is_deactivated and user != request.user %}
<a href="{{ url_for("user.deactivate", id=user.id) }}" class="btn btn-secondary">{{ _("Deactivate") }}</a>
{% elif user.id and user.is_deactivated %}
<a href="{{ url_for("user.activate", id=user.id) }}" class="btn btn-primary">{{ _("Activate") }}</a>
{% else %}
<a href="#" class="btn btn-secondary disabled">{{ _("Deactivate") }}</a>
{% endif %}
{% if user.id and user != request.user %}
<a href="{{ url_for("user.delete", id=user.id) }}" onClick='return confirm({{_("Are you sure?")|tojson}});' class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a>
{% else %}
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a>
{% endif %}
</div></div>
<ul class="nav nav-tabs pt-2 border-0" id="tablist" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="true">{{_("Profile")}}</a>
</li>
<li class="nav-item">
<a class="nav-link" id="roles-tab" data-toggle="tab" href="#roles" role="tab" aria-controls="roles" aria-selected="false">{{_("Roles")}}</a>
</li>
</ul>
<div class="tab-content border mb-2 pt-2" id="tabcontent">
<div class="tab-pane fade show active" id="profile" role="tabpanel" aria-labelledby="roles-tab">
<div class="form-group col">
<label for="user-uid">
{{_('User ID')}}
{% if user.is_service_user %}
<span class="badge badge-secondary">{{_('service')}}</span>
{% endif %}
</label>
{% if user.id %}
<input type="number" class="form-control" id="user-uid" name="uid" value="{{ user.unix_uid }}" readonly>
{% else %}
<input type="text" class="form-control" id="user-uid" name="uid" placeholder="{{_('will be choosen')}}" readonly>
{% endif %}
</div>
{% if not user.id %}
<div class="form-group col">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="user-serviceaccount" name="serviceaccount" value="1" aria-label="enabled">
<label class="form-check-label" for="user-serviceaccount">{{_('Service User')}}</label>
</div>
</div>
{% endif %}
<div class="form-group col">
<label for="user-loginname">{{_("Login Name")}}</label>
<input type="text" class="form-control" id="user-loginname" name="loginname" value="{{ user.loginname or '' }}" {% if user.id %}readonly{% endif %}>
<small class="form-text text-muted">
{{_("Only letters, numbers, dashes (\"-\") and underscores (\"_\") are allowed. At most 32, at least 2 characters. There is a word blocklist. Must be unique.")}}
</small>
</div>
{% if not user.id %}
<div class="form-group col">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="ignore-loginname-blocklist" name="ignore-loginname-blocklist" value="1" aria-label="enabled">
<label class="form-check-label" for="ignore-loginname-blocklist">{{_('Ignore login name blocklist')}}</label>
</div>
</div>
{% endif %}
<div class="form-group col">
<label for="user-loginname">{{_("Display Name")}}</label>
<input type="text" class="form-control" id="user-displayname" name="displayname" value="{{ user.displayname or '' }}">
<small class="form-text text-muted">
{{_("If you leave this empty it will be set to the login name. At most 128, at least 2 characters. No character restrictions.")}}
</small>
</div>
{% if not user.id %}
<div class="form-group col">
<label for="user-email">{{_("E-Mail Address")}}</label>
<input type="email" class="form-control" id="user-email" name="email" value="">
<small class="form-text text-muted">
{{_("Make sure the address is correct! Services might use e-mail addresses as account identifiers and rely on them being unique and verified.")}}
</small>
</div>
{% else %}
<div class="form-group col">
<span>{{_("E-Mail Addresses")}}</span>
<table class="table table-sm mt-2">
<thead>
<tr>
<th scope="col">{{_("Address")}}</th>
<th scope="col" class="text-center">{{_("Verified")}}</th>
<th scope="col" class="text-center">{{_("Delete")}}</th>
</tr>
</thead>
<tbody id="email-rows">
{% for email in user.all_emails %}
<tr>
<td>
<input type="hidden" name="email-{{ email.id }}-present" value="1">
{{ email.address }}
{% if email == user.primary_email %}
<span class="badge badge-primary">{{ _('primary') }}</span>
{% endif %}
</td>
<td class="text-center">
<input type="checkbox" value="1" name="email-{{ email.id }}-verified"{{ ' checked disabled' if email.verified }}>
</td>
<td class="text-center">
<input type="checkbox" value="1" name="email-{{ email.id }}-delete"{{ ' disabled' if email == user.primary_email }}>
</td>
</tr>
{% endfor %}
{{ new_email_row(0) }}
</tbody>
</table>
<small class="form-text text-muted">
{{_("Make sure that addresses you add are correct! Services might use e-mail addresses as account identifiers and rely on them being unique and verified.")}}
</small>
</div>
<div class="form-group col">
<label>{{_("Primary E-Mail Address")}}</label>
<select name="primary_email" class="form-control">
{% for email in user.all_emails if email.verified %}
<option value="{{ email.id }}"{{ ' selected' if email == user.primary_email }}>{{ email.address }}</option>
{% endfor %}
</select>
</div>
<div class="form-group col">
<label>{{_("Recovery E-Mail Address")}}</label>
<select name="recovery_email" class="form-control">
<option value="primary"{{ ' selected' if not user.recovery_email }}>{{ _('Use primary address') }}</option>
{% for email in user.all_emails if email.verified %}
<option value="{{ email.id }}" {{ 'selected' if email == user.recovery_email }}>{{ email.address }}</option>
{% endfor %}
</select>
</div>
{% for service_user in user.service_users if service_user.has_email_preferences %}
<div class="form-group col">
<label>{{ _("Address for %(name)s", name=service_user.service.name) }}</label>
<select name="service_{{ service_user.service.id }}_email" class="form-control">
<option value="primary" {{ 'selected' if not service_user.service_email }}>{{ _('Use primary address') }}</option>
{% for email in user.all_emails if email.verified %}
<option value="{{ email.id }}" {{ 'selected' if email == service_user.service_email }}>{{ email.address }}</option>
{% endfor %}
</select>
</div>
{% endfor %}
{% endif %}
<div class="form-group col">
<label for="user-loginname">{{_("Password")}}</label>
{% if user.id %}
<input type="password" autocomplete="new-password" class="form-control" id="user-password" name="password" placeholder="●●●●●●●●" minlength={{ User.PASSWORD_MINLEN }} maxlength={{ User.PASSWORD_MAXLEN }} pattern="{{ User.PASSWORD_REGEX }}">
{% else %}
<input type="text" class="form-control" id="user-password" name="password" placeholder="{{_("E-Mail to set it will be sent")}}" readonly>
{% endif %}
<small class="form-text text-muted">
{{ User.PASSWORD_DESCRIPTION|safe }}
</small>
</div>
{% if user.id %}
<div class="form-group col">
<div class="mb-1">{{_("Two-Factor Authentication")}}</div>
<p>
{{ _("Status:") }} {{ _("Enabled") if user.mfa_enabled else _("Disabled") }}<br>
{{ user.mfa_recovery_codes|length }} {{ _("Recovery Codes") }}, {{ user.mfa_totp_methods|length }} {{ _("Authenticator Apps (TOTP)") }}, {{ user.mfa_webauthn_methods|length }} {{ _("U2F and FIDO2 Devices") }}
</p>
<a href="{{ url_for("user.disable_mfa", id=user.id) }}" onClick='return confirm({{_("Are you sure?")|tojson}});' class="btn btn-secondary">{{_("Reset 2FA")}}</a>
</div>
<div class="form-group col">
<div class="mb-1">{{_("Sessions")}}</div>
<p>{{ _("%(session_count)d active sessions", session_count=user.sessions|rejectattr('expired')|list|length) }}</p>
<a href="{{ url_for("user.revoke_sessions", id=user.id) }}" onClick='return confirm({{_("Are you sure?")|tojson}});' class="btn btn-secondary">{{_("Revoke all sessions")}}</a>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="roles" role="tabpanel" aria-labelledby="roles-tab">
<div class="form-group col">
<span>{{_("Roles")}}:</span>
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">{{_("Name")}}</th>
<th scope="col">{{_("Description")}}</th>
</tr>
</thead>
<tbody>
{% for role in roles|sort(attribute="name") %}
<tr id="role-{{ role.id }}">
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="role-{{ role.id }}-checkbox" name="role-{{ role.id }}" value="1" aria-label="enabled"
{% if user in role.members %}checked {% endif %}
{% if role.is_default and not user.is_service_user %}disabled {% endif %}>
</div>
</td>
<td>
<a href="{{ url_for("role.show", roleid=role.id) }}">
{{ role.name }}
</a>
</td>
<td>
{{ role.description }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="form-group col">
<span>{{_("Resulting groups (only updated after save)")}}:</span>
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">{{_("Name")}}</th>
<th scope="col">{{_("Description")}}</th>
</tr>
</thead>
<tbody>
{% for group in user.groups|sort(attribute="name") %}
<tr id="group-{{ group.id }}">
<td>
<a href="{{ url_for("group.show", id=group.id) }}">
{{ group.name }}
</a>
</td>
<td>
{{ group.description }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</form>
<script>
$('#email-rows').on('click', '.delete-new-email-row', function () {
$(this).closest('tr').remove()
});
let new_email_id = 1;
$('#email-rows').on('input', 'tr:last input', function () {
$('#email-rows tr:last button.delete-new-email-row').removeClass('d-none');
$('#email-rows').append({{ new_email_row('TMPID')|tojson }}.replace(/TMPID/g, new_email_id));
new_email_id ++;
});
</script>
{% endblock %}
File added
# German translations for uffd.
# Copyright (C) 2021 ORGANIZATION
# This file is distributed under the same license as the uffd project.
# Milan Höllner <milan.hoellner@posteo.de>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-03-24 18:37+0100\n"
"PO-Revision-Date: 2021-05-25 21:18+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.10.3\n"
#: uffd/models/invite.py:84 uffd/models/invite.py:107 uffd/models/invite.py:112
msgid "Invite link is invalid"
msgstr "Einladungslink ist ungültig"
#: uffd/models/invite.py:86
msgid "Invite link does not grant any roles"
msgstr "Einladungslink weist keine Rollen zu"
#: uffd/models/invite.py:88
msgid "Invite link does not grant any new roles"
msgstr "Einladungslink weist keine neuen Rollen zu"
#: uffd/models/invite.py:93 uffd/models/signup.py:122
#: uffd/templates/mfa/setup.html:225
msgid "Success"
msgstr "Erfolgreich"
#: uffd/models/ratelimit.py:76
msgid "a few seconds"
msgstr "ein paar Sekunden"
#: uffd/models/ratelimit.py:78
msgid "30 seconds"
msgstr "30 Sekunden"
#: uffd/models/ratelimit.py:80
msgid "one minute"
msgstr "eine Minute"
#: uffd/models/ratelimit.py:82
#, python-format
msgid "%(minutes)d minutes"
msgstr "%(minutes)d Minuten"
#: uffd/models/ratelimit.py:84
msgid "one hour"
msgstr "eine Stunde"
#: uffd/models/ratelimit.py:85
#, python-format
msgid "%(hours)d hours"
msgstr "%(hours)d Stunden"
#: uffd/models/session.py:62 uffd/models/session.py:74
#: uffd/models/session.py:80 uffd/models/session.py:90
msgid "Unknown"
msgstr "Unbekannt"
#: uffd/models/signup.py:78 uffd/models/signup.py:103
msgid "Invalid signup request"
msgstr "Ungültiger Account-Registrierungs-Link"
#: uffd/models/signup.py:80
msgid "Login name is invalid"
msgstr "Anmeldename ist ungültig"
#: uffd/models/signup.py:82
msgid "Display name is invalid"
msgstr "Anzeigename ist ungültig"
#: uffd/models/signup.py:84 uffd/views/selfservice.py:112 uffd/views/user.py:51
#: uffd/views/user.py:99
msgid "E-Mail address is invalid"
msgstr "Ungültige E-Mail-Adresse"
#: uffd/models/signup.py:86 uffd/views/selfservice.py:49
msgid "Invalid password"
msgstr "Passwort ungültig"
#: uffd/models/signup.py:88 uffd/models/signup.py:107
msgid "A user with this login name already exists"
msgstr "Ein Account mit diesem Anmeldenamen existiert bereits"
#: uffd/models/signup.py:89
msgid "Valid"
msgstr "Gültig"
#: uffd/models/signup.py:105 uffd/views/signup.py:104
msgid "Wrong password"
msgstr "Falsches Passwort"
#: uffd/models/signup.py:115 uffd/views/user.py:62
msgid "Login name or e-mail address is already in use"
msgstr "Der Anmeldename oder die E-Mail-Adresse wird bereits verwendet"
#: uffd/models/user.py:119
#, python-format
msgid ""
"At least %(minlen)d and at most %(maxlen)d characters. Only letters, "
"digits, spaces and some symbols (<code>%(symbols)s</code>) allowed. "
"Please use a password manager."
msgstr ""
"%(minlen)d bis %(maxlen)d Zeichen. Nur Buchstaben, Ziffern, Leerzeichen "
"und manche Symbole (<code>%(symbols)s</code>), keine Umlaute. Bitte "
"verwende einen Passwort-Manager."
#: uffd/templates/403.html:10
msgid "Access Denied"
msgstr "Zugriff verweigert"
#: uffd/templates/403.html:17
msgid "You don't have the permission to access this page."
msgstr "Du bist nicht berechtigt, auf diese Seite zuzugreifen."
#: uffd/templates/base.html:85
msgid "Change"
msgstr "Ändern"
#: uffd/templates/base.html:93 uffd/templates/session/deviceauth.html:12
msgid "Authorize Device Login"
msgstr "Gerätelogin erlauben"
#: uffd/templates/base.html:94 uffd/templates/session/devicelogin.html:6
msgid "Device Login"
msgstr "Gerätelogin"
#: uffd/templates/base.html:100 uffd/templates/oauth2/logout.html:5
msgid "Logout"
msgstr "Abmelden"
#: uffd/templates/base.html:107 uffd/templates/service/overview.html:15
#: uffd/templates/session/login.html:6 uffd/templates/session/login.html:20
msgid "Login"
msgstr "Anmelden"
#: uffd/templates/base.html:143
msgid "About uffd"
msgstr "Über uffd"
#: uffd/templates/group/list.html:8 uffd/templates/invite/list.html:6
#: uffd/templates/mail/list.html:8 uffd/templates/role/list.html:8
#: uffd/templates/service/index.html:8 uffd/templates/service/show.html:106
#: uffd/templates/service/show.html:134 uffd/templates/user/list.html:8
msgid "New"
msgstr "Neu"
#: uffd/templates/group/list.html:14
msgid "GID"
msgstr "GID"
#: uffd/templates/group/list.html:15 uffd/templates/group/show.html:26
#: uffd/templates/invite/new.html:35 uffd/templates/mail/list.html:14
#: uffd/templates/mail/show.html:7 uffd/templates/mfa/setup.html:98
#: uffd/templates/mfa/setup.html:99 uffd/templates/mfa/setup.html:107
#: uffd/templates/mfa/setup.html:157 uffd/templates/mfa/setup.html:158
#: uffd/templates/mfa/setup.html:169 uffd/templates/role/list.html:14
#: uffd/templates/rolemod/list.html:9 uffd/templates/rolemod/show.html:44
#: uffd/templates/selfservice/self.html:239
#: uffd/templates/service/index.html:14 uffd/templates/service/show.html:20
#: uffd/templates/service/show.html:140 uffd/templates/user/show.html:212
#: uffd/templates/user/show.html:244
msgid "Name"
msgstr "Name"
#: uffd/templates/group/list.html:16 uffd/templates/group/show.html:33
#: uffd/templates/invite/new.html:36 uffd/templates/role/list.html:15
#: uffd/templates/role/show.html:48 uffd/templates/rolemod/list.html:10
#: uffd/templates/rolemod/show.html:26 uffd/templates/selfservice/self.html:240
#: uffd/templates/user/show.html:213 uffd/templates/user/show.html:245
msgid "Description"
msgstr "Beschreibung"
#: uffd/templates/group/show.html:8 uffd/templates/mail/show.html:27
#: uffd/templates/role/show.html:13 uffd/templates/rolemod/show.html:8
#: uffd/templates/service/api.html:15 uffd/templates/service/oauth2.html:15
#: uffd/templates/service/show.html:16 uffd/templates/user/show.html:31
msgid "Save"
msgstr "Speichern"
#: uffd/templates/group/show.html:9 uffd/templates/invite/new.html:56
#: uffd/templates/mail/show.html:28 uffd/templates/mfa/auth.html:33
#: uffd/templates/role/show.html:14 uffd/templates/rolemod/show.html:9
#: uffd/templates/service/api.html:9 uffd/templates/service/oauth2.html:9
#: uffd/templates/service/show.html:10
#: uffd/templates/session/deviceauth.html:39
#: uffd/templates/session/deviceauth.html:49
#: uffd/templates/session/devicelogin.html:29 uffd/templates/user/show.html:32
msgid "Cancel"
msgstr "Abbrechen"
#: uffd/templates/group/show.html:11 uffd/templates/role/show.html:19
#: uffd/templates/role/show.html:21 uffd/templates/selfservice/self.html:61
#: uffd/templates/selfservice/self.html:210
#: uffd/templates/selfservice/self.html:254 uffd/templates/service/api.html:11
#: uffd/templates/service/oauth2.html:11 uffd/templates/service/show.html:12
#: uffd/templates/user/show.html:41 uffd/templates/user/show.html:193
#: uffd/templates/user/show.html:199
msgid "Are you sure?"
msgstr "Wirklich fortfahren?"
#: uffd/templates/group/show.html:11 uffd/templates/group/show.html:13
#: uffd/templates/mail/show.html:30 uffd/templates/mail/show.html:32
#: uffd/templates/mfa/setup.html:117 uffd/templates/mfa/setup.html:179
#: uffd/templates/role/show.html:21 uffd/templates/role/show.html:24
#: uffd/templates/selfservice/self.html:62 uffd/templates/service/api.html:12
#: uffd/templates/service/oauth2.html:12 uffd/templates/service/show.html:13
#: uffd/templates/user/show.html:41 uffd/templates/user/show.html:43
#: uffd/templates/user/show.html:116
msgid "Delete"
msgstr "Löschen"
#: uffd/templates/group/show.html:18
msgid "Group ID"
msgstr "Gruppen ID"
#: uffd/templates/group/show.html:29 uffd/templates/signup/start.html:18
msgid ""
"At least one and at most 32 lower-case characters, digits, dashes (\"-\")"
" or underscores (\"_\"). <b>Cannot be changed later!</b>"
msgstr ""
"1 bis 32 Kleinbuchstaben, Zahlen, Binde- (\"-\") und Unterstriche "
"(\"_\"). <b>Kann später nicht geändert werden!</b>"
#: uffd/templates/group/show.html:40 uffd/templates/role/show.html:71
#: uffd/templates/rolemod/show.html:16
msgid "Members"
msgstr "Mitglieder"
#: uffd/templates/invite/list.html:12
msgid "Link"
msgstr "Link"
#: uffd/templates/invite/list.html:13
msgid "Created by"
msgstr "Erstellt durch"
#: uffd/templates/invite/list.html:14 uffd/templates/service/api.html:34
#: uffd/templates/service/show.html:141
msgid "Permissions"
msgstr "Berechtigungen"
#: uffd/templates/invite/list.html:15
msgid "Usages"
msgstr "Verwendungen"
#: uffd/templates/invite/list.html:16
msgid "Status"
msgstr "Status"
#: uffd/templates/invite/list.html:26
msgid "Copy link to clipboard"
msgstr "Link kopieren"
#: uffd/templates/invite/list.html:27
msgid "Show link as QR code"
msgstr "Link als QR-Code anzeigen"
#: uffd/templates/invite/list.html:40
msgid "Signup"
msgstr "Account-Registrierung"
#: uffd/templates/invite/list.html:44
msgid "user signups"
msgstr "Account-Registrierungen"
#: uffd/templates/invite/list.html:49 uffd/templates/user/show.html:190
msgid "Disabled"
msgstr "Deaktiviert"
#: uffd/templates/invite/list.html:51
msgid "Voided"
msgstr "Verbraucht"
#: uffd/templates/invite/list.html:53
msgid "Expired"
msgstr "Abgelaufen"
#: uffd/templates/invite/list.html:55
msgid "Invalid, unpermitted creator"
msgstr "Ungültig, erstellt durch unberechtigten Account"
#: uffd/templates/invite/list.html:57
msgid "Invalid"
msgstr "Ungültig"
#: uffd/templates/invite/list.html:59
#, python-format
msgid "Valid once, expires %(expiry_date)s"
msgstr "Einmal verwendbar, gültig bis %(expiry_date)s"
#: uffd/templates/invite/list.html:61
#, python-format
msgid "Valid, expires %(expiry_date)s"
msgstr "Gültig bis %(expiry_date)s"
#: uffd/templates/invite/list.html:78
msgid "Invite Link Details"
msgstr "Details zum Einladungslink"
#: uffd/templates/invite/list.html:85
msgid "Type:"
msgstr "Typ:"
#: uffd/templates/invite/list.html:85
msgid "Single-use"
msgstr "Einmal verwendbar"
#: uffd/templates/invite/list.html:85 uffd/templates/invite/new.html:9
msgid "Multi-use"
msgstr "Mehrfach verwendbar"
#: uffd/templates/invite/list.html:86
msgid "Created:"
msgstr "Erstellt:"
#: uffd/templates/invite/list.html:87
msgid "Expires:"
msgstr "Ablaufdatum:"
#: uffd/templates/invite/list.html:88
msgid "Permissions:"
msgstr "Berechtigungen:"
#: uffd/templates/invite/list.html:91 uffd/templates/invite/new.html:21
msgid "Link allows account registration"
msgstr "Link erlaubt Account-Registrierung"
#: uffd/templates/invite/list.html:93 uffd/templates/invite/new.html:22
msgid "No account registration allowed"
msgstr "Keine Account-Registrierung möglich"
#: uffd/templates/invite/list.html:96
#, python-format
msgid "Link grants users the role \"%(name)s\""
msgstr "Link gibt Accounts die Rolle \"%(name)s\""
#: uffd/templates/invite/list.html:102
msgid "Never used"
msgstr "Keine Verwendungen"
#: uffd/templates/invite/list.html:106
#, python-format
msgid "Registration of user <a href=\"%(user_url)s\">%(user_name)s</a>"
msgstr "Account-Registrierung von <a href=\"%(user_url)s\">%(user_name)s</a>"
#: uffd/templates/invite/list.html:109
#, python-format
msgid "Roles granted to <a href=\"%(user_url)s\">%(user_name)s</a>"
msgstr "Rollen an <a href=\"%(user_url)s\">%(user_name)s</a> vergeben"
#: uffd/templates/invite/list.html:120
msgid "Disable Link"
msgstr "Link deaktivieren"
#: uffd/templates/invite/list.html:124
msgid "Reenable Link"
msgstr "Link reaktivieren"
#: uffd/templates/invite/list.html:138
msgid "Invite"
msgstr "Einladungslink"
#: uffd/templates/invite/new.html:6
msgid "Link Type"
msgstr "Link Typ"
#: uffd/templates/invite/new.html:8
msgid "Valid for a single successful use"
msgstr "Für eine erfolgreiche Verwendung gültig"
#: uffd/templates/invite/new.html:13
msgid "Valid Until"
msgstr "Ablaufdatum"
#: uffd/templates/invite/new.html:15
#, python-format
msgid "Must be within the next %(max_valid_days)d days"
msgstr "Muss innerhalb der nächsten %(max_valid_days)d Tage liegen"
#: uffd/templates/invite/new.html:19 uffd/templates/signup/start.html:6
msgid "Account Registration"
msgstr "Account-Registrierung"
#: uffd/templates/invite/new.html:30
msgid "Granted Roles"
msgstr "Enthaltene Rollen"
#: uffd/templates/invite/new.html:55
msgid "Create Link"
msgstr "Link erstellen"
#: uffd/templates/invite/use.html:5
msgid "Invite Link"
msgstr "Einladungslink"
#: uffd/templates/invite/use.html:8
#, python-format
msgid "Welcome to the %(org_name)s Single-Sign-On!"
msgstr "Willkommen im %(org_name)s Single-Sign-On!"
#: uffd/templates/invite/use.html:12
msgid ""
"With this link you can register a new user account with the following "
"roles or add the roles to an existing account:"
msgstr ""
"Mit diesem Link kannst du einen Account mit den folgenden Rollen "
"erstellen oder diese Rollen zu einem existierenden Account hinzufügen:"
#: uffd/templates/invite/use.html:14
msgid "With this link you can add the following roles to an existing account:"
msgstr ""
"Mit diesem Link kannst du die folgenden Rollen zu einem existierenden "
"Account hinzufügen:"
#: uffd/templates/invite/use.html:16
msgid "With this link you can register a new user account."
msgstr "Mit diesem Link kannst du einen Account registieren."
#: uffd/templates/invite/use.html:28
msgid "Add the roles to your account now"
msgstr "Rollen jetzt zu deinem Account hinzufügen"
#: uffd/templates/invite/use.html:30
msgid "Logout and switch to a different account"
msgstr "Abmelden und zu einem anderen Account wechseln"
#: uffd/templates/invite/use.html:33
msgid "Logout to register a new account"
msgstr "Abmelden um einen neuen Account zu registrieren"
#: uffd/templates/invite/use.html:37
msgid "Register a new account"
msgstr "Neuen Account registrieren"
#: uffd/templates/invite/use.html:40
msgid "Login and add the roles to your account"
msgstr "Anmelden und die Rollen zu deinem Account hinzufügen"
#: uffd/templates/mail/list.html:15 uffd/templates/mail/show.html:13
msgid "Receiving addresses"
msgstr "Empfangsadressen"
#: uffd/templates/mail/list.html:16 uffd/templates/mail/show.html:20
msgid "Destinations"
msgstr "Zieladressen"
#: uffd/templates/mail/show.html:16
msgid ""
"One address pattern (local+ext@domain, local@domain, local, @domain) per "
"line. Only lower-case ASCII letters, digits and symbols."
msgstr ""
"Ein Adressmuster (local+ext@domain, local@domain, local, @domain) pro "
"Zeile. Nur ASCII-Kleinbuchstaben, -Ziffern und -Symbole."
#: uffd/templates/mail/show.html:23
msgid "One address per line"
msgstr "Eine Adresse pro Zeile"
#: uffd/templates/mfa/auth.html:6 uffd/templates/selfservice/self.html:159
#: uffd/templates/user/show.html:188
msgid "Two-Factor Authentication"
msgstr "Zwei-Faktor-Authentifizierung"
#: uffd/templates/mfa/auth.html:11
msgid "Enable javascript for authentication with U2F/FIDO2 devices"
msgstr "Aktiviere Javascript zur Authentifizierung mit U2F/FIDO2 Geräten"
#: uffd/templates/mfa/auth.html:15
msgid "Authentication with U2F/FIDO2 devices is not supported by your browser"
msgstr ""
"Authentifizierung mit U2F/FIDO2 Geräten wird von deinem Browser nicht "
"unterstützt"
#: uffd/templates/mfa/auth.html:21
msgid "Authenticate with U2F/FIDO2 device"
msgstr "Authentifiziere dich mit einem U2F/FIDO2-Gerät"
#: uffd/templates/mfa/auth.html:24
msgid "or"
msgstr "oder"
#: uffd/templates/mfa/auth.html:27
msgid "Code from your authenticator app or recovery code"
msgstr "Code aus deiner Authentifikator-App oder Wiederherstellungscode"
#: uffd/templates/mfa/auth.html:30
msgid "Verify"
msgstr "Verifizieren"
#: uffd/templates/mfa/auth.html:43 uffd/templates/mfa/setup.html:199
msgid "Contacting server"
msgstr "Verbinde mit Server"
#: uffd/templates/mfa/auth.html:52 uffd/templates/mfa/auth.html:80
msgid "Session timed out"
msgstr "Sitzung abgelaufen"
#: uffd/templates/mfa/auth.html:54
msgid "You have not registered any U2F/FIDO2 devices for your account"
msgstr "Es sind keine U2F/FIDO2-Geräte für deinen Account registriert"
#: uffd/templates/mfa/auth.html:56 uffd/templates/mfa/setup.html:208
msgid "Server error"
msgstr "Serverfehler"
#: uffd/templates/mfa/auth.html:59 uffd/templates/mfa/setup.html:210
msgid "Waiting for device"
msgstr "Warte auf Gerät"
#: uffd/templates/mfa/auth.html:62
msgid "Verifing response"
msgstr "Überprüfe Antwort"
#: uffd/templates/mfa/auth.html:76
msgid "Success, redirecting"
msgstr "Erfolg, leite weiter"
#: uffd/templates/mfa/auth.html:82 uffd/templates/mfa/setup.html:228
msgid "Invalid response from device"
msgstr "Ungültige Antwort von Gerät"
#: uffd/templates/mfa/auth.html:88
msgid "Authentication timed out, was aborted or not allowed"
msgstr "Authentifikation abgelaufen, abgebrochen oder nicht erlaubt"
#: uffd/templates/mfa/auth.html:90
msgid "Device is not registered for your account"
msgstr "Gerät ist nicht für deinen Account registriert"
#: uffd/templates/mfa/auth.html:92
msgid "Authentication was aborted"
msgstr "Authentifikation abgebrochen"
#: uffd/templates/mfa/auth.html:94 uffd/templates/mfa/setup.html:240
#: uffd/templates/mfa/setup.html:264
msgid "U2F and FIDO2 devices are not supported by your browser"
msgstr "U2F- und FIDO2-Geräte werden vom Webbrowser nicht unterstüzt"
#: uffd/templates/mfa/auth.html:97 uffd/templates/mfa/setup.html:243
msgid "Could not connect to server"
msgstr "Verbindung zum Server fehlgeschlagen"
#: uffd/templates/mfa/auth.html:103
msgid "Authentication failed "
msgstr "Authentifikation fehlgeschlagen"
#: uffd/templates/mfa/auth.html:106
msgid "Retry authenticate with U2F/FIDO2 device"
msgstr "Authentifikation mit U2F/FIDO2-Gerät nochmal versuchen"
#: uffd/templates/mfa/disable.html:6
msgid ""
"When you proceed, all recovery codes, registered authenticator "
"applications and devices will be invalidated.\n"
"\tYou can later generate new recovery codes and setup your applications "
"and devices again."
msgstr ""
"Wenn du fortfährst werden alle Wiederherstellungscodes, registrierte "
"Authentifikator-Apps und Geräte ungültig gemacht. Du kannst später neue "
"Wiederherstellungscodes generieren und das Setup der Anwendungen und "
"Geräte erneut durchführen."
#: uffd/templates/mfa/disable.html:11 uffd/templates/mfa/setup.html:32
msgid "Disable two-factor authentication"
msgstr "Zwei-Faktor-Authentifizierung (2FA) deaktivieren"
#: uffd/templates/mfa/setup.html:18 uffd/templates/selfservice/self.html:165
msgid "Two-factor authentication is currently <strong>enabled</strong>."
msgstr "Die Zwei-Faktor-Authentifizierung ist derzeit <strong>aktiviert</strong>."
#: uffd/templates/mfa/setup.html:20 uffd/templates/selfservice/self.html:167
msgid "Two-factor authentication is currently <strong>disabled</strong>."
msgstr ""
"Die Zwei-Faktor-Authentifizierung ist derzeit "
"<strong>deaktiviert</strong>."
#: uffd/templates/mfa/setup.html:23
msgid ""
"You need to generate recovery codes and setup at least one authentication"
" method to enable two-factor authentication."
msgstr ""
"Du musst Wiederherstellungscodes generieren und mindestens eine "
"Authentifizierungsmethode hinzufügen um Zwei-Faktor-Authentifizierung "
"nutzen zu können."
#: uffd/templates/mfa/setup.html:25
msgid ""
"You need to setup at least one authentication method to enable two-factor"
" authentication."
msgstr ""
"Du musst mindestens eine Authentifizierungsmethode hinzufügen um Zwei-"
"Faktor-Authentifizierung nutzen zu können."
#: uffd/templates/mfa/setup.html:36
msgid "Reset two-factor configuration"
msgstr "Zwei-Faktor-Authentifizierung zurücksetzen"
#: uffd/templates/mfa/setup.html:46 uffd/templates/mfa/setup_recovery.html:5
#: uffd/templates/user/show.html:191
msgid "Recovery Codes"
msgstr "Wiederherstellungscodes"
#: uffd/templates/mfa/setup.html:48
msgid ""
"Recovery codes allow you to login and setup new two-factor methods when "
"you lost your registered second factor."
msgstr ""
"Wiederherstellungscodes erlauben die Anmeldung und das erneute Hinzufügen"
" einer Zwei-Faktor-Methode, falls der Zweite Faktor verloren geht."
#: uffd/templates/mfa/setup.html:52
msgid ""
"You need to setup recovery codes before you can setup up authenticator "
"apps or U2F/FIDO2 devices."
msgstr ""
"Du musst Wiederherstellungscodes generieren bevor du einen "
"Authentifikator-App oder ein U2F/FIDO2 Gerät hinzufen kannst."
#: uffd/templates/mfa/setup.html:54
msgid "Each code can only be used once."
msgstr "Jeder Code kann nur einmal verwendet werden."
#: uffd/templates/mfa/setup.html:62
msgid "Generate recovery codes to enable two-factor authentication"
msgstr ""
"Generiere Wiederherstellungscodes um die Zwei-Faktor-Authentifizierung zu"
" aktivieren"
#: uffd/templates/mfa/setup.html:66
msgid "Generate new recovery codes"
msgstr "Generiere neue Wiederherstellungscodes"
#: uffd/templates/mfa/setup.html:75
msgid "You have no remaining recovery codes."
msgstr "Du hast keine Wiederherstellungscodes übrig."
#: uffd/templates/mfa/setup.html:85 uffd/templates/user/show.html:191
msgid "Authenticator Apps (TOTP)"
msgstr "Authentifikator-Apps (TOTP)"
#: uffd/templates/mfa/setup.html:87
msgid "Use an authenticator application on your mobile device as a second factor."
msgstr "Nutze eine Authentifikator-App auf deinem Mobilgerät als zweiten Faktor."
#: uffd/templates/mfa/setup.html:90
msgid ""
"The authenticator app generates a 6-digit one-time code each time you "
"login.\n"
"\t\t\tCompatible apps are freely available for most phones."
msgstr ""
"Die Authentifikator-App generiert ein 6-stelliges Einmalpasswort für "
"jeden Login. Passende Apps sind kostenlos verfügbar für die meisten "
"Mobilgeräte."
#: uffd/templates/mfa/setup.html:100
msgid "Setup new app"
msgstr "Neue App hinzufügen"
#: uffd/templates/mfa/setup.html:108 uffd/templates/mfa/setup.html:170
msgid "Registered On"
msgstr "Registriert am"
#: uffd/templates/mfa/setup.html:122
msgid "No authenticator apps registered yet"
msgstr "Bisher keine Authentifikator-Apps registriert"
#: uffd/templates/mfa/setup.html:134 uffd/templates/user/show.html:191
msgid "U2F and FIDO2 Devices"
msgstr "U2F und FIDO2 Geräte"
#: uffd/templates/mfa/setup.html:136
msgid "Use an U2F or FIDO2 compatible hardware security key as a second factor."
msgstr "Nutze einen U2F oder FIDO2 kompatiblen Key als zweiten Faktor."
#: uffd/templates/mfa/setup.html:139
msgid ""
"U2F and FIDO2 devices are not supported by all browsers and can be "
"particularly difficult to use on mobile\n"
"\t\t\tdevices. <strong>It is strongly recommended to also setup an "
"authenticator app</strong> to be able to login on all\n"
"\t\t\tbrowsers."
msgstr ""
"U2F und FIDO2 Geräte werden nicht von allen Browsern unterstützt und "
"können besonders auf mobilen Geräten schwer zu nutzen sein. <strong>Es "
"wird dringend empfohlen ebenfalls eine Authentifikator-App "
"hinzuzufügen</strong> um einen Login mit allen Browsern zu ermöglichen."
#: uffd/templates/mfa/setup.html:147
msgid "U2F/FIDO2 support not enabled"
msgstr "U2F/FIDO2 Unterstützung nicht aktiviert"
#: uffd/templates/mfa/setup.html:151
msgid "Enable javascript in your browser to use U2F and FIDO2 devices!"
msgstr ""
"Aktiviere Javascript in deinem Browser, um U2F und FIDO2 Geräte nutzen zu"
" können!"
#: uffd/templates/mfa/setup.html:161
msgid "Setup new device"
msgstr "Neues Gerät hinzufügen"
#: uffd/templates/mfa/setup.html:184
msgid "No U2F/FIDO2 devices registered yet"
msgstr "Bisher kein U2F/FIDO2 Gerät registriert"
#: uffd/templates/mfa/setup.html:207
msgid "You need to generate recovery codes first"
msgstr "Du musst erst Wiederherstellungscodes generieren"
#: uffd/templates/mfa/setup.html:234
msgid "Registration timed out, was aborted or not allowed"
msgstr "Registrierung abgelaufen, abgebrochen oder nicht erlaubt"
#: uffd/templates/mfa/setup.html:236
msgid "Device already registered"
msgstr "Gerät bereits registriert"
#: uffd/templates/mfa/setup.html:238
msgid "Registration was aborted"
msgstr "Registrierung abgebrochen"
#: uffd/templates/mfa/setup.html:249
msgid "Registration failed"
msgstr "Registrierung fehlgeschlagen"
#: uffd/templates/mfa/setup.html:252
msgid "Retry registration"
msgstr "Registrierung nochmal versuchen"
#: uffd/templates/mfa/setup_recovery.html:8
msgid ""
"Recovery codes allow you to login when you lose access to your "
"authenticator app or U2F/FIDO device. Each code can\n"
"\tonly be used once."
msgstr ""
"Wiederherstellungscodes erlauben den Login, wenn der Zugriff auf die "
"Authentifikator-App oder das U2F/FIDO2 Gerät verloren geht. Jeder Code "
"kann nur einmal verwendet werden."
#: uffd/templates/mfa/setup_recovery.html:21
msgid ""
"These are your new recovery codes. Make sure to store them in a safe "
"place or you risk losing access to your\n"
"\taccount. All previous recovery codes are now invalid."
msgstr ""
"Dies sind deine Wiederherstellungscodes. Speichere sie an einem sicheren "
"Ort, sonst könntest du den Zugriff auf dein Konto verlieren. Alle "
"vorherigen Wiederherstellungscodes sind nun ungültig."
#: uffd/templates/mfa/setup_recovery.html:26
#: uffd/templates/session/deviceauth.html:36
#: uffd/templates/session/devicelogin.html:25
msgid "Continue"
msgstr "Weiter"
#: uffd/templates/mfa/setup_recovery.html:28
msgid "Download codes"
msgstr "Codes herunterladen"
#: uffd/templates/mfa/setup_recovery.html:30
msgid "Print codes"
msgstr "Codes ausdrucken"
#: uffd/templates/mfa/setup_totp.html:6
msgid ""
"Install an authenticator application on your mobile device like FreeOTP "
"or Google Authenticator and scan this QR\n"
"\tcode. On Apple devices you can use an app called \"Authenticator\"."
msgstr ""
"Installiere eine Authentifikator-App auf deinem Mobilgerät wie FreeOTP "
"oder Google Authenticator and scanne diesen QR Code. Auf Geräten von "
"Apple kann die App \"Authenticator\" verwendet werden."
#: uffd/templates/mfa/setup_totp.html:18
msgid ""
"If you are on your mobile device and cannot scan the code, you can click "
"on it to open it with your\n"
"\t\t\tauthenticator app. If that does not work, enter the following "
"details manually into your authenticator\n"
"\t\t\tapp:"
msgstr ""
"Falls du ein Mobilgerät verwendest und den Code nicht scannen kannst, "
"kannst du drauf klicken um ihn damit direkt in der Authentifikator-App zu"
" öffnen. Wenn das nicht funktioniert, gib die folgenden Angaben manuell "
"in die Authentifikator-App ein:"
#: uffd/templates/mfa/setup_totp.html:23
msgid "Issuer"
msgstr "Herausgeber"
#: uffd/templates/mfa/setup_totp.html:24
msgid "Account"
msgstr "Konto"
#: uffd/templates/mfa/setup_totp.html:25
msgid "Secret"
msgstr "Geheimnis"
#: uffd/templates/mfa/setup_totp.html:26
msgid "Type"
msgstr "Typ"
#: uffd/templates/mfa/setup_totp.html:27
msgid "Digits"
msgstr "Zeichen"
#: uffd/templates/mfa/setup_totp.html:28
msgid "Hash algorithm"
msgstr "Hash-Algorithmus"
#: uffd/templates/mfa/setup_totp.html:29
msgid "Interval/period"
msgstr "Intervall/Dauer"
#: uffd/templates/mfa/setup_totp.html:29
msgid "seconds"
msgstr "Sekunden"
#: uffd/templates/mfa/setup_totp.html:37
msgid "Code"
msgstr "Code"
#: uffd/templates/mfa/setup_totp.html:38
msgid "Verify and complete setup"
msgstr "Verifiziere und beende das Setup"
#: uffd/templates/oauth2/logout.html:10
msgid "Javascript is required for automatic logout"
msgstr "Für das automatische Abmelden muss Javascript aktiviert sein"
#: uffd/templates/oauth2/logout.html:12
msgid ""
"While you successfully logged out of the Single-Sign-On service, you may "
"still be logged in on these services:"
msgstr ""
"Während du nun aus dem Single-Sign-On abgemeldet bist, bist du eventuell "
"weiterhin in folgenden Diensten angemeldet:"
#: uffd/templates/oauth2/logout.html:25
msgid ""
"Please wait until you have been automatically logged out of all services "
"or make sure of this yourself."
msgstr ""
"Bitte warte, bis das automatische Abmelden bei allen Diensten "
"abgeschlossen ist oder melde dich überall manuell ab."
#: uffd/templates/oauth2/logout.html:29
msgid "Logging you out on all services ..."
msgstr "Abmeldung bei allen Diensten ..."
#: uffd/templates/oauth2/logout.html:33
msgid "Skip this and continue"
msgstr "Automatisches Abmelden überspringen"
#: uffd/templates/oauth2/logout.html:72
msgid "Done, redirecting ..."
msgstr "Abgeschlossen, leite weiter ..."
#: uffd/templates/oauth2/logout.html:76
msgid "Log out failed on some services. Retry?"
msgstr ""
"Automatisches Abmelden bei einigen Diensten fehlgeschlagen. Nochmal "
"versuchen?"
#: uffd/templates/role/list.html:23
msgid "<empty name>"
msgstr "<leerer Name>"
#: uffd/templates/role/show.html:6
msgid ""
"Name, moderator group, included roles and groups of this role are managed"
" externally."
msgstr ""
"Name, Moderationsgruppe, enthaltene Rollen und Gruppen dieser Rolle "
"werden extern verwaltet."
#: uffd/templates/role/show.html:17
msgid ""
"All non-service users will be removed as members from this role and get "
"its permissions implicitly. Are you sure?"
msgstr ""
"Alle Nicht-Service-Accounts verlieren diese Rolle und erhalten dessen "
"Berechtigungen implizit."
#: uffd/templates/role/show.html:17 uffd/templates/role/show.html:23
msgid "Set as default"
msgstr "Als Default setzen"
#: uffd/templates/role/show.html:19
msgid "Unset as default"
msgstr "Nicht mehr als Default setzen"
#: uffd/templates/role/show.html:29
msgid "Settings"
msgstr "Einstellungen"
#: uffd/templates/role/show.html:32
msgid "Included roles"
msgstr "Enthaltene Rollen"
#: uffd/templates/role/show.html:35 uffd/templates/role/show.html:122
msgid "Included groups"
msgstr "Enthaltene Gruppen"
#: uffd/templates/role/show.html:42
msgid "Role Name"
msgstr "Rollenname"
#: uffd/templates/role/show.html:54
msgid "Moderator Group"
msgstr "Moderationsgruppe"
#: uffd/templates/role/show.html:56
msgid "No Moderator Group"
msgstr "Keine Moderationsgruppe"
#: uffd/templates/role/show.html:63
msgid "Moderators"
msgstr "Accounts mit Moderationsrechten"
#: uffd/templates/role/show.html:81
msgid "Roles to include groups from recursively"
msgstr "Rollen, deren Gruppen rekursiv enthalten sein sollen"
#: uffd/templates/role/show.html:86 uffd/templates/role/show.html:127
msgid "name"
msgstr "Name"
#: uffd/templates/role/show.html:87 uffd/templates/role/show.html:128
msgid "description"
msgstr "Beschreibung"
#: uffd/templates/role/show.html:88
msgid "currently includes groups"
msgstr "derzeit enthaltene Gruppen"
#: uffd/templates/role/show.html:129
msgid "2FA required"
msgstr "2FA erforderlich"
#: uffd/templates/rolemod/show.html:7
msgid "Invite Members"
msgstr "Mitglieder einladen"
#: uffd/templates/rolemod/show.html:13
msgid "Overview"
msgstr "Übersicht"
#: uffd/templates/rolemod/show.html:22
msgid "Role name"
msgstr "Rollenname"
#: uffd/templates/rolemod/show.html:30
msgid "Moderators:"
msgstr "Accounts mit Moderationsrechten:"
#: uffd/templates/rolemod/show.html:40
msgid "Role members:"
msgstr "Mitglieder:"
#: uffd/templates/rolemod/show.html:53
msgid "Remove"
msgstr "Entfernen"
#: uffd/templates/selfservice/forgot_password.html:6
msgid "Forgot password"
msgstr "Passwort vergessen"
#: uffd/templates/selfservice/forgot_password.html:9
#: uffd/templates/selfservice/self.html:21 uffd/templates/session/login.html:12
#: uffd/templates/signup/start.html:9 uffd/templates/user/list.html:18
#: uffd/templates/user/show.html:78
msgid "Login Name"
msgstr "Anmeldename"
#: uffd/templates/selfservice/forgot_password.html:13
msgid "Mail Address"
msgstr "E-Mail-Adresse"
#: uffd/templates/selfservice/forgot_password.html:17
msgid "Send password reset mail"
msgstr "Passwort-Zurücksetzen-Mail versenden"
#: uffd/templates/selfservice/self.html:7
msgid ""
"Some permissions require you to setup two-factor authentication.\n"
"\tThese permissions are not in effect until you do that."
msgstr ""
"Einige deiner Berechtigungen erfordern das Einrichten von Zwei-Faktor-"
"Authentifizierung.\n"
"\tDiese Berechtigungen werden erst aktiv, wenn du dies getan hast."
#: uffd/templates/selfservice/self.html:14 uffd/templates/user/show.html:48
msgid "Profile"
msgstr "Profil"
#: uffd/templates/selfservice/self.html:15
msgid ""
"Your profile information is used by all services that are integrated into"
" the Single-Sign-On."
msgstr ""
"Deine Profilangaben werden von allen Diensten verwendet, die an das "
"Single-Sign-On angeschlossen sind."
#: uffd/templates/selfservice/self.html:16
msgid "Changes may take several minutes to be visible in all services."
msgstr "Änderungen sind erst nach einigen Minuten in allen Diensten sichtbar."
#: uffd/templates/selfservice/self.html:25 uffd/templates/signup/start.html:22
#: uffd/templates/user/list.html:19 uffd/templates/user/show.html:93
msgid "Display Name"
msgstr "Anzeigename"
#: uffd/templates/selfservice/self.html:28
msgid "Update Profile"
msgstr "Änderungen speichern"
#: uffd/templates/selfservice/self.html:37 uffd/templates/user/show.html:110
msgid "E-Mail Addresses"
msgstr "E-Mail-Adressen"
#: uffd/templates/selfservice/self.html:38
msgid ""
"Add and delete addresses associated with your account. You will need to "
"verify new addresses by opening a link set to them."
msgstr ""
"Füge neue Adressen zu deinem Account hinzu oder löschen vorhandene. Neue "
"Adressen müssen bestätigt werden, bevor sie verwendet werden können. Dazu"
" erhälst du eine E-Mail mit einem Bestätigungslink."
#: uffd/templates/selfservice/self.html:43
msgid "Email"
msgstr "E-Mail"
#: uffd/templates/selfservice/self.html:44
msgid "New E-Mail Address"
msgstr "Neue E-Mail-Adresse"
#: uffd/templates/selfservice/self.html:45
msgid "Add address"
msgstr "Adresse hinzufügen"
#: uffd/templates/selfservice/self.html:55 uffd/templates/user/show.html:126
msgid "primary"
msgstr "primär"
#: uffd/templates/selfservice/self.html:57
msgid "unverified"
msgstr "nicht bestätigt"
#: uffd/templates/selfservice/self.html:62 uffd/views/selfservice.py:175
msgid "Cannot delete primary e-mail address"
msgstr "Primäre E-Mail-Adresse kann nicht gelöscht werden"
#: uffd/templates/selfservice/self.html:65
msgid "Retry verification"
msgstr "Bestätigungslink neusenden"
#: uffd/templates/selfservice/self.html:81
msgid "E-Mail Preferences"
msgstr "E-Mail-Einstellungen"
#: uffd/templates/selfservice/self.html:83
msgid ""
"Choose your primary e-mail address and the address password recovery "
"e-mails will be sent to."
msgstr ""
"Wähle deine primäre Adresse und die Adresse für Passwort-"
"Zurücksetzen-E-Mails aus."
#: uffd/templates/selfservice/self.html:85
msgid "You can also select different addresses for different services."
msgstr ""
"Du kannst für unterschiedliche Dienste unterschiedliche Adressen "
"verwenden."
#: uffd/templates/selfservice/self.html:88
msgid "Adresses must be verified before you can select them here."
msgstr "Adressen müssen bestätigt sein, damit du sie hier auswählen kannst."
#: uffd/templates/selfservice/self.html:93
msgid "Primary Address"
msgstr "Primäre Adresse"
#: uffd/templates/selfservice/self.html:101
msgid "Address for Password Reset E-Mails"
msgstr "Adresse für Passwort-Zurücksetzen-E-Mails"
#: uffd/templates/selfservice/self.html:103
#: uffd/templates/selfservice/self.html:116 uffd/templates/user/show.html:155
#: uffd/templates/user/show.html:165
msgid "Use primary address"
msgstr "Primäre Adresse verwenden"
#: uffd/templates/selfservice/self.html:114
#, python-format
msgid "Address for Service \"%(name)s\""
msgstr "Adresse für Dienst „%(name)s“"
#: uffd/templates/selfservice/self.html:125
msgid "Show more settings ..."
msgstr "Weitere Einstellungen anzeigen ..."
#: uffd/templates/selfservice/self.html:127
msgid "Update E-Mail Preferences"
msgstr "E-Mail-Einstellungen speichern"
#: uffd/templates/selfservice/self.html:136
#: uffd/templates/session/login.html:16 uffd/templates/signup/start.html:36
#: uffd/templates/user/show.html:175
msgid "Password"
msgstr "Passwort"
#: uffd/templates/selfservice/self.html:137
msgid ""
"Your login password for the Single-Sign-On. Only enter it on the Single-"
"Sign-On login page! No other legit websites will ask you for this "
"password. We do not ever need your password to assist you."
msgstr ""
"Dein Passwort zur Anmeldung im Single-Sign-On. Gib dieses Passwort "
"ausschließlich auf der Anmeldeseite des Single-Sign-Ons ein! Keine andere"
" Webseite wird dich nach diesem Passwort fragen. Es wird auch niemals für"
" Support-Anfragen benötigt."
#: uffd/templates/selfservice/self.html:142
#: uffd/templates/selfservice/set_password.html:9
msgid "New Password"
msgstr "Neues Passwort"
#: uffd/templates/selfservice/self.html:148
#: uffd/templates/selfservice/set_password.html:16
#: uffd/templates/signup/start.html:43
msgid "Repeat Password"
msgstr "Passwort wiederholen"
#: uffd/templates/selfservice/self.html:150
msgid "Change Password"
msgstr "Passwort ändern"
#: uffd/templates/selfservice/self.html:160
msgid ""
"Setting up Two-Factor Authentication (2FA) adds an additional step to the"
" Single-Sign-On login and increases the security of your account "
"significantly."
msgstr ""
"Zwei-Faktor-Authentifizierung (2FA) fügt einen zusätzlichen Schritt zur "
"Anmeldung im Single-Sign-On hinzu und verbessert damit die Sicherheit "
"deines Accounts erheblich."
#: uffd/templates/selfservice/self.html:170
msgid "Manage two-factor authentication"
msgstr "Zwei-Faktor-Authentifizierung (2FA) verwalten"
#: uffd/templates/selfservice/self.html:178
msgid "Active Sessions"
msgstr "Aktive Sitzungen"
#: uffd/templates/selfservice/self.html:179
msgid "Your active login sessions on this device and other devices."
msgstr "Deine aktiven Sitzungen auf diesem und anderen Geräten."
#: uffd/templates/selfservice/self.html:180
msgid ""
"Revoke a session to log yourself out on another device. Note that this is"
" limited to the Single-Sign-On session and <b>does not affect login "
"sessions on services.</b>"
msgstr ""
"Widerrufe eine Sitzung, um dich auf einem anderen Gerät abzumelden. "
"Beachte dass dies auf deine Sitzung im Single-Sign-On beschränkt ist und "
"sich <b>nicht auf Sitzungen an Diensten auswirkt.</b>"
#: uffd/templates/selfservice/self.html:186
msgid "Last used"
msgstr "Zuletzt verwendet"
#: uffd/templates/selfservice/self.html:187
msgid "Device"
msgstr "Gerät"
#: uffd/templates/selfservice/self.html:193
#: uffd/templates/selfservice/self.html:202
msgid "Just now"
msgstr "Gerade eben"
#: uffd/templates/selfservice/self.html:211
msgid "Revoke"
msgstr "Widerrufen"
#: uffd/templates/selfservice/self.html:227 uffd/templates/user/list.html:20
#: uffd/templates/user/show.html:51 uffd/templates/user/show.html:207
#: uffd/views/role.py:21
msgid "Roles"
msgstr "Rollen"
#: uffd/templates/selfservice/self.html:228
msgid ""
"Aside from a set of base permissions, your roles determine the "
"permissions of your account."
msgstr ""
"Deine Berechtigungen werden, von einigen Basis-Berechtigungen abgesehen, "
"von deinen Rollen bestimmt"
#: uffd/templates/selfservice/self.html:230
#, python-format
msgid ""
"See <a href=\"%(services_url)s\">Services</a> for an overview of your "
"current permissions."
msgstr ""
"Auf <a href=\"%(services_url)s\">Dienste</a> erhälst du einen Überblick "
"über deine aktuellen Berechtigungen."
#: uffd/templates/selfservice/self.html:234
msgid "Administrators and role moderators can invite you to new roles."
msgstr ""
"Accounts mit Adminrechten oder Rollen-Moderationsrechten können dich zu "
"Rollen einladen."
#: uffd/templates/selfservice/self.html:249
msgid ""
"Some permissions in this role require you to setup two-factor "
"authentication"
msgstr ""
"Einige Berechtigungen dieser Rolle erfordern das Einrichten von Zwei-"
"Faktor-Authentifikation"
#: uffd/templates/selfservice/self.html:255
msgid "Leave"
msgstr "Verlassen"
#: uffd/templates/selfservice/self.html:262
msgid "You currently don't have any roles"
msgstr "Du hast derzeit keine Rollen"
#: uffd/templates/selfservice/set_password.html:6
msgid "Reset password"
msgstr "Passwort zurücksetzen"
#: uffd/templates/selfservice/set_password.html:20
msgid "Set password"
msgstr "Passwort setzen"
#: uffd/templates/service/api.html:20
msgid "Authentication Username"
msgstr "Authentifikations-Name"
#: uffd/templates/service/api.html:25
msgid "Authentication Password"
msgstr "Authentifikations-Passwort"
#: uffd/templates/service/api.html:37
msgid "Access user and group data"
msgstr "Zugriff auf Account- und Gruppen-Daten"
#: uffd/templates/service/api.html:41
msgid "Verify user passwords"
msgstr "Passwörter von Nutzeraccounts verifizieren"
#: uffd/templates/service/api.html:45
msgid "Access mail aliases"
msgstr "Zugriff auf Mail-Weiterleitungen"
#: uffd/templates/service/api.html:49
msgid "Resolve remailer addresses"
msgstr "Auflösen von Remailer-Adressen"
#: uffd/templates/service/api.html:51 uffd/templates/service/show.html:55
msgid "This option has no effect: Remailer config options are unset"
msgstr "Diese Option hat keine Auswirkung: Remailer ist nicht konfiguriert"
#: uffd/templates/service/api.html:56
msgid "Access uffd metrics"
msgstr "Zugriff auf uffd-Metriken"
#: uffd/templates/service/oauth2.html:20 uffd/templates/service/show.html:112
msgid "Client ID"
msgstr "Client-ID"
#: uffd/templates/service/oauth2.html:25
msgid "Client Secret"
msgstr "Client-Secret"
#: uffd/templates/service/oauth2.html:34
msgid "Redirect URIs"
msgstr "Redirect-URIs"
#: uffd/templates/service/oauth2.html:37
msgid "One URI per line"
msgstr "Eine URI pro Zeile"
#: uffd/templates/service/oauth2.html:42
msgid "Logout URIs"
msgstr "Abmelde-URIs"
#: uffd/templates/service/oauth2.html:49
msgid "One URI per line, prefixed with space-separated method (GET/POST)"
msgstr ""
"Eine URI pro Zeile, vorangestellt die mit Leerzeichen getrennte HTTP-"
"Methode (GET/POST)"
#: uffd/templates/service/overview.html:11
msgid ""
"Some services may not be publicly listed! Log in to see all services you "
"have access to."
msgstr ""
"Einige Dienste sind eventuell nicht öffentlich aufgelistet! Melde dich an"
" um alle Dienste zu sehen, auf die du Zugriff hast."
#: uffd/templates/service/overview.html:36
#, python-format
msgid "Logo for %(service_title)s"
msgstr "Logo für %(service_title)s"
#: uffd/templates/service/overview.html:55
msgid "No access"
msgstr "Kein Zugriff"
#: uffd/templates/service/overview.html:75
msgid "Manage OAuth2 and API clients"
msgstr "OAuth2- und API-Clients verwalten"
#: uffd/templates/service/overview.html:95 uffd/templates/user/list.html:61
#: uffd/templates/user/list.html:82
msgid "Close"
msgstr "Schließen"
#: uffd/templates/service/show.html:24
msgid "Access Restriction"
msgstr "Zugriffsbeschränkungen"
#: uffd/templates/service/show.html:26
msgid "No user has access"
msgstr "Kein Account hat Zugriff"
#: uffd/templates/service/show.html:27
msgid "All users have access (legacy)"
msgstr "Alle Account haben Zugriff (veraltet)"
#: uffd/templates/service/show.html:29
#, python-format
msgid "Members of group \"%(group_name)s\" have access"
msgstr "Mitglieder der Gruppe \"%(group_name)s\" haben Zugriff"
#: uffd/templates/service/show.html:37
msgid "Hide deactivated users from service"
msgstr "Deaktivierte Nutzer verstecken"
#: uffd/templates/service/show.html:44
msgid ""
"Allow users with access to select a different e-mail address for this "
"service"
msgstr ""
"Ermögliche Nutzern mit Zugriff auf diesen Dienst eine andere E-Mail-"
"Adresse auszuwählen"
#: uffd/templates/service/show.html:46
msgid "If disabled, the service always uses the primary e-mail address."
msgstr "Wenn deaktiviert, wird immer die primäre E-Mail-Adresse verwendet."
#: uffd/templates/service/show.html:53
msgid "Hide e-mail addresses with remailer"
msgstr "E-Mail-Adressen mit Remailer verstecken"
#: uffd/templates/service/show.html:60 uffd/templates/service/show.html:83
msgid "Remailer disabled"
msgstr "Remailer deaktiviert"
#: uffd/templates/service/show.html:63 uffd/templates/service/show.html:86
msgid "Remailer enabled"
msgstr "Remailer aktiviert"
#: uffd/templates/service/show.html:66 uffd/templates/service/show.html:89
msgid "Remailer enabled (deprecated, case-sensitive format)"
msgstr ""
"Remailer aktiviert (veraltetes, Groß-/Kleinschreibung-unterscheidendes "
"Format)"
#: uffd/templates/service/show.html:70
msgid ""
"Some services notify users about changes to their e-mail address. "
"Modifying this setting immediatly affects the e-mail addresses of all "
"users and can cause masses of notification e-mails."
msgstr ""
"Einige Dienste benachrichtigen Nutzer bei Änderungen ihrer E-Mail-"
"Adresse. Diese Einstellung zu verändern wirkt sich unmittelbar auf die E"
"-Mail-Adressen aller Nutzer aus und kann zu massenhaftem Versand von "
"Benachrichtigungs-E-Mails führen."
#: uffd/templates/service/show.html:76
msgid "Overwrite remailer setting for specific users"
msgstr "Überschreibe Remailer-Einstellung für ausgewählte Nutzer"
#: uffd/templates/service/show.html:79
msgid "Login names"
msgstr "Anmeldenamen"
#: uffd/templates/service/show.html:94
msgid ""
"Useful for testing remailer before enabling it for all users. Specify "
"users as a comma-seperated list of login names."
msgstr ""
"Hilfreich zum Testen des Remailers vor dem Aktivieren für alle Nutzer. Um"
" Nutzer auszuwählen, liste ihre Anmeldenamen mit Komma getrennt auf."
#: uffd/templates/session/deviceauth.html:15
msgid "Log into a service on another device without entering your password."
msgstr ""
"Melde dich an einem Dienst auf einem anderen Gerät an ohne dein Password "
"eingeben zu müssen."
#: uffd/templates/session/deviceauth.html:18
#: uffd/templates/session/devicelogin.html:13
msgid "Initiation Code"
msgstr "Startcode"
#: uffd/templates/session/deviceauth.html:27
#: uffd/templates/session/devicelogin.html:18
msgid "Confirmation Code"
msgstr "Bestätigungscode"
#: uffd/templates/session/deviceauth.html:33
msgid ""
"Start logging into a service on the other device and chose \"Device "
"Login\" on the login page. Enter the displayed initiation code in the box"
" above."
msgstr ""
"Beginne die Anmeldung an einem Dienst auf dem anderen Gerät und wähle "
"\"Gerätelogin\" auf der Anmeldeseite aus. Gib den angezeigten Startcode "
"oben ein."
#: uffd/templates/session/deviceauth.html:43
#, python-format
msgid "Authorize the login for service <b>%(service_name)s</b>?"
msgstr "Anmeldung an Dienst <b>%(service_name)s</b> erlauben?"
#: uffd/templates/session/deviceauth.html:46
msgid "Authorize Login"
msgstr "Anmeldung erlauben"
#: uffd/templates/session/deviceauth.html:53
msgid ""
"Enter the confirmation code on the other device and complete the login. "
"Click <em>Finish</em> afterwards."
msgstr ""
"Gib den Bestätigungscode auf dem anderen Gerät ein und schließe die "
"Anmeldung ab. Clicke danach auf <em>Abschließen</em>."
#: uffd/templates/session/deviceauth.html:56
msgid "Finish"
msgstr "Beenden"
#: uffd/templates/session/devicelogin.html:9
msgid ""
"Use a login session on another device (e.g. your laptop) to log into a "
"service without entering your password."
msgstr ""
"Nutze eine Login-Sitzung auf einem anderen Gerät (z.B. deinem Laptop) um "
"dich bei einem Dienst anzumelden."
#: uffd/templates/session/devicelogin.html:22
#, python-format
msgid ""
"Open <code><a href=\"%(deviceauth_url)s\">%(deviceauth_url)s</a></code> "
"on the other device and enter the initiation code there. Then enter the "
"confirmation code in the box above."
msgstr ""
"Öffne <code><a href=\"%(deviceauth_url)s\">%(deviceauth_url)s</a></code> "
"auf dem anderen Gerät und gib dort den obenstehenden Startcode ein. Geben"
" anschließend den Bestätigungscode hier ein."
#: uffd/templates/session/login.html:23
msgid "- or -"
msgstr "- oder -"
#: uffd/templates/session/login.html:25
msgid "Login with another device"
msgstr "Über anderes Gerät anmelden"
#: uffd/templates/session/login.html:30
msgid "Register"
msgstr "Registrieren"
#: uffd/templates/session/login.html:32
msgid "Forgot Password?"
msgstr "Passwort vergessen?"
#: uffd/templates/signup/confirm.html:6
msgid "Complete Registration"
msgstr "Account-Registrierung abschließen"
#: uffd/templates/signup/confirm.html:9
msgid "Please enter your password to complete the account registration"
msgstr "Bitte gib dein Passwort ein, um die Account-Registrierung abzuschließen"
#: uffd/templates/signup/confirm.html:13
msgid "Complete Account Registration"
msgstr "Account-Registrierung abschließen"
#: uffd/templates/signup/start.html:13
msgid "Check"
msgstr "Überprüfen"
#: uffd/templates/signup/start.html:25
msgid "At least one and at most 128 characters, no other special requirements."
msgstr "Mindestens 1 und maximal 128 Zeichen, keine weiteren Einschränkungen."
#: uffd/templates/signup/start.html:29 uffd/templates/user/show.html:102
msgid "E-Mail Address"
msgstr "E-Mail-Adresse"
#: uffd/templates/signup/start.html:32
msgid ""
"We will send a confirmation mail to this address that you need to "
"complete the registration."
msgstr ""
"Wir werden eine Bestätigungsmail an diese Adresse senden. Du benötigst "
"sie, um die Account-Registrierung abzuschließen."
#: uffd/templates/signup/start.html:47
msgid "Create Account"
msgstr "Account registrieren"
#: uffd/templates/signup/start.html:74
msgid "The name is already taken"
msgstr "Dieser Name wird bereits verwendet"
#: uffd/templates/signup/start.html:77
msgid "Too many requests! Please wait a bit before trying again!"
msgstr "Zu viele Anfragen! Bitte warte etwas, bevor du es erneut versuchst!"
#: uffd/templates/signup/start.html:80
msgid "The name is invalid"
msgstr "Name ungültig"
#: uffd/templates/signup/submitted.html:5
msgid "Confirm your E-Mail Address"
msgstr "E-Mail-Adresse bestätigen"
#: uffd/templates/signup/submitted.html:7
#, python-format
msgid ""
"We sent a confirmation mail to <b>%(signup_mail)s</b>. You need to "
"confirm your mail address within 48 hours to complete the account "
"registration."
msgstr ""
"Eine Bestätigungsmail wurde an <b>%(signup_mail)s</b> gesendet. Du must "
"deine E-Mail-Adresse innerhalb von 48 Stunden bestätigen, um die Account-"
"Registrierung abzuschließen."
#: uffd/templates/signup/submitted.html:8
msgid ""
"If you mistyped your mail address or don't receive the confirmation mail "
"for another reason, retry the registration procedure from the beginning."
msgstr ""
"Falls du dich bei deiner E-Mail-Adresse vertippt hast oder du aus anderen"
" Gründen keine Bestätigungsmail erhalten hast, kannst du den Prozess "
"einfach von Vorne beginnen."
#: uffd/templates/user/list.html:11
msgid "CSV import"
msgstr "CSV-Import"
#: uffd/templates/user/list.html:17
msgid "UID"
msgstr "UID"
#: uffd/templates/user/list.html:34 uffd/templates/user/show.html:60
msgid "service"
msgstr "service"
#: uffd/templates/user/list.html:37
msgid "deactivated"
msgstr "deaktiviert"
#: uffd/templates/user/list.html:60
msgid "Import a csv formated list of users"
msgstr "Importiere eine als CSV formatierte Liste von Accounts"
#: uffd/templates/user/list.html:67
msgid ""
"The format should be \"loginname,mailaddres,roleid1;roleid2\". Neither "
"setting the display name nor setting passwords is supported (yet). "
"Example:"
msgstr ""
"Das Format sollte \"loginname,mailaddres,roleid1;roleid2\" sein. Der "
"Anzeigename oder das Password können (derzeit) nicht gesetzt werden. "
"Beispiel:"
#: uffd/templates/user/list.html:78 uffd/templates/user/show.html:88
msgid "Ignore login name blocklist"
msgstr "Liste der nicht erlaubten Anmeldenamen ignorieren"
#: uffd/templates/user/list.html:83
msgid "Import"
msgstr "Importieren"
#: uffd/templates/user/show.html:6
msgid "New address"
msgstr "Neue Adresse"
#: uffd/templates/user/show.html:27
msgid ""
"This account is deactivated. The user cannot login and existing sessions "
"are not usable. The user cannot log into services, but existing sessions "
"on services might still be active."
msgstr ""
"Dieser Account ist deaktiviert. Der Nutzer kann sich nicht neu anmelden. "
"Existierende Sitzungen sind nicht nutzbar. Eine Anmeldung bei Diensten "
"ist nicht möglich, allerdings könnten bestehende Sitzungen weiterhin "
"aktiv sein."
#: uffd/templates/user/show.html:34 uffd/templates/user/show.html:38
msgid "Deactivate"
msgstr "Deaktivieren"
#: uffd/templates/user/show.html:36
msgid "Activate"
msgstr "Aktivieren"
#: uffd/templates/user/show.html:58
msgid "User ID"
msgstr "Account ID"
#: uffd/templates/user/show.html:66
msgid "will be choosen"
msgstr "wird automatisch bestimmt"
#: uffd/templates/user/show.html:73
msgid "Service User"
msgstr "Service-Account"
#: uffd/templates/user/show.html:81
msgid ""
"Only letters, numbers, dashes (\"-\") and underscores (\"_\") are "
"allowed. At most 32, at least 2 characters. There is a word blocklist. "
"Must be unique."
msgstr ""
"Nur Buchstaben, Zahlen, Binde- (\"-\") und Unterstriche (\"_\") sind "
"erlaubt. Maximal 32, mindestens 2 Zeichen. Es gibt eine Liste nicht "
"erlaubter Namen. Muss einmalig sein."
#: uffd/templates/user/show.html:96
msgid ""
"If you leave this empty it will be set to the login name. At most 128, at"
" least 2 characters. No character restrictions."
msgstr ""
"Wenn das Feld leer bleibt, wird der Anmeldename verwendet. Maximal 128, "
"mindestens 2 Zeichen. Keine Zeichenbeschränkung."
#: uffd/templates/user/show.html:105
msgid ""
"Make sure the address is correct! Services might use e-mail addresses as "
"account identifiers and rely on them being unique and verified."
msgstr ""
"Stelle sicher, dass die Adresse korrekt ist! Manche Dienste verwenden die"
" E-Mail-Adresse um Accounts zu identifizieren und verlassen sich darauf, "
"dass diese verifiziert und einzigartig sind."
#: uffd/templates/user/show.html:114
msgid "Address"
msgstr "Adresse"
#: uffd/templates/user/show.html:115
msgid "Verified"
msgstr "Verifiziert"
#: uffd/templates/user/show.html:141
msgid ""
"Make sure that addresses you add are correct! Services might use e-mail "
"addresses as account identifiers and rely on them being unique and "
"verified."
msgstr ""
"Stelle sicher, dass Adressen, die du hinzufügst, korrekt sind! Manche "
"Dienste verwenden die E-Mail-Adresse um Accounts zu identifizieren und "
"verlassen sich darauf, dass diese verifiziert und einzigartig sind."
#: uffd/templates/user/show.html:145
msgid "Primary E-Mail Address"
msgstr "Primäre E-Mail-Adresse"
#: uffd/templates/user/show.html:153
msgid "Recovery E-Mail Address"
msgstr "Wiederherstellungs-E-Mail-Adresse"
#: uffd/templates/user/show.html:163
#, python-format
msgid "Address for %(name)s"
msgstr "Adresse für %(name)s"
#: uffd/templates/user/show.html:179
msgid "E-Mail to set it will be sent"
msgstr "Mail zum Setzen wird versendet"
#: uffd/templates/user/show.html:190
msgid "Status:"
msgstr "Status:"
#: uffd/templates/user/show.html:190
msgid "Enabled"
msgstr "Aktiv"
#: uffd/templates/user/show.html:193
msgid "Reset 2FA"
msgstr "2FA zurücksetzen"
#: uffd/templates/user/show.html:197
msgid "Sessions"
msgstr "Sitzungen"
#: uffd/templates/user/show.html:198
#, python-format
msgid "%(session_count)d active sessions"
msgstr "%(session_count)d aktive Sitzungen"
#: uffd/templates/user/show.html:199
msgid "Revoke all sessions"
msgstr "Alle Sitzungen widerrufen"
#: uffd/templates/user/show.html:240
msgid "Resulting groups (only updated after save)"
msgstr "Resultierende Gruppen (wird nur aktualisiert beim Speichern)"
#: uffd/views/group.py:22
msgid "Groups"
msgstr "Gruppen"
#: uffd/views/group.py:42
msgid "GID is already in use or was used in the past"
msgstr "GID wird oder wurde bereits verwendet"
#: uffd/views/group.py:45
msgid "Invalid name"
msgstr "Ungültiger Name"
#: uffd/views/group.py:56
msgid "Group with this name or id already exists"
msgstr "Gruppe mit diesem Namen oder dieser ID existiert bereits"
#: uffd/views/group.py:61
msgid "Group created"
msgstr "Gruppe erstellt"
#: uffd/views/group.py:63
msgid "Group updated"
msgstr "Gruppe aktualisiert"
#: uffd/views/group.py:72
msgid "Deleted group"
msgstr "Gruppe gelöscht"
#: uffd/views/invite.py:43
msgid "Invites"
msgstr "Einladungslinks"
#: uffd/views/invite.py:75
msgid "The \"Expires After\" date is too far in the future"
msgstr "Das Ablaufdatum liegt zu weit in der Zukunft"
#: uffd/views/invite.py:78
msgid "You are not allowed to create invite links with these permissions"
msgstr "Dir fehlen Berechtigungen um diesen Einladungslink zu erstellen"
#: uffd/views/invite.py:81
msgid "Invite link must either allow signup or grant at least one role"
msgstr ""
"Einladungslink muss entweder Account-Registrierung erlauben oder Rollen "
"vergeben"
#: uffd/views/invite.py:111 uffd/views/invite.py:146
msgid "Invalid invite link"
msgstr "Ungültiger Einladungslink"
#: uffd/views/invite.py:129
msgid "Roles successfully updated"
msgstr "Rollen erfolgreich geändert"
#: uffd/views/invite.py:149
msgid "Invite link does not allow signup"
msgstr "Einladungslink erlaubt keine Account-Registrierung"
#: uffd/views/invite.py:175 uffd/views/selfservice.py:44
#: uffd/views/signup.py:47
msgid "Passwords do not match"
msgstr "Die Passwörter stimmen nicht überein"
#: uffd/views/invite.py:181 uffd/views/signup.py:53
#, python-format
msgid "Too many signup requests with this mail address! Please wait %(delay)s."
msgstr ""
"Zu viele Account-Registrierungen mit dieser E-Mail-Adresse! Bitte warte "
"%(delay)s."
#: uffd/views/invite.py:184 uffd/views/signup.py:56
#, python-format
msgid "Too many requests! Please wait %(delay)s."
msgstr "Zu viele Anfragen! Bitte warte %(delay)s."
#: uffd/views/invite.py:199 uffd/views/signup.py:74
msgid "Could not send mail"
msgstr "Mailversand fehlgeschlagen"
#: uffd/views/mail.py:21
msgid "Forwardings"
msgstr "Weiterleitungen"
#: uffd/views/mail.py:46
#, python-format
msgid "Invalid receive address: %(mail_address)s"
msgstr "Ungültige Empfangsadresse: %(mail_address)s"
#: uffd/views/mail.py:50
msgid "Mail mapping updated."
msgstr "Mailweiterleitung geändert."
#: uffd/views/mail.py:59
msgid "Deleted mail mapping."
msgstr "Mailweiterleitung gelöscht."
#: uffd/views/mfa.py:49
msgid "Two-factor authentication was reset"
msgstr "Zwei-Faktor-Authentifizierung wurde zurückgesetzt"
#: uffd/views/mfa.py:78
msgid "Generate recovery codes first!"
msgstr "Generiere zuerst die Wiederherstellungscodes!"
#: uffd/views/mfa.py:86
msgid "Code is invalid"
msgstr "Wiederherstellungscode ist ungültig"
#: uffd/views/mfa.py:105
#, python-format
msgid ""
"2FA WebAuthn support disabled because import of the fido2 module failed "
"(%s)"
msgstr ""
"2FA WebAuthn Unterstützung deaktiviert, da das fido2 Modul nicht geladen "
"werden konnte (%s)"
#: uffd/views/mfa.py:216
#, python-format
msgid "We received too many invalid attempts! Please wait at least %s."
msgstr "Wir haben zu viele fehlgeschlagene Versuche! Bitte warte mindestens %s."
#: uffd/views/mfa.py:231
msgid "You have exhausted your recovery codes. Please generate new ones now!"
msgstr "Du hast keine Wiederherstellungscode mehr. Bitte generiere diese jetzt!"
#: uffd/views/mfa.py:234
msgid ""
"You only have a few recovery codes remaining. Make sure to generate new "
"ones before they run out."
msgstr ""
"Du hast nur noch wenige Wiederherstellungscodes übrig. Bitte generiere "
"diese erneut bevor keine mehr übrig sind."
#: uffd/views/mfa.py:238
msgid "Two-factor authentication failed"
msgstr "Zwei-Faktor-Authentifizierung fehlgeschlagen"
#: uffd/views/oauth2.py:267 uffd/views/selfservice.py:66
#: uffd/views/session.py:86
#, python-format
msgid ""
"We received too many requests from your ip address/network! Please wait "
"at least %(delay)s."
msgstr ""
"Wir haben zu viele Anfragen von deiner IP-Adresses bzw. aus deinem "
"Netzwerk empfangen! Bitte warte mindestens %(delay)s."
#: uffd/views/oauth2.py:278
msgid "Device login is currently not available. Try again later!"
msgstr "Geräte-Login ist gerade nicht verfügbar. Versuche es später nochmal!"
#: uffd/views/oauth2.py:296
msgid "Device login failed"
msgstr "Gerätelogin fehlgeschlagen"
#: uffd/views/oauth2.py:304
msgid "You need to login to access this service"
msgstr "Du musst dich anmelden, um auf diesen Dienst zugreifen zu können"
#: uffd/views/oauth2.py:335
#, python-format
msgid ""
"You don't have the permission to access the service "
"<b>%(service_name)s</b>."
msgstr ""
"Du bist nicht berechtigt, auf den Dienst <b>%(service_name)s</b> "
"zuzugreifen."
#: uffd/views/role.py:68
msgid "Locked roles cannot be deleted"
msgstr "Gesperrte Rollen können nicht gelöscht werden"
#: uffd/views/rolemod.py:22
msgid "Moderation"
msgstr "Moderation"
#: uffd/views/rolemod.py:42
msgid "Description too long"
msgstr "Beschreibung zu lang"
#: uffd/views/rolemod.py:59
msgid "Member removed"
msgstr "Mitglied entfernt"
#: uffd/views/selfservice.py:22
msgid "Selfservice"
msgstr "Selfservice"
#: uffd/views/selfservice.py:33
msgid "Display name changed."
msgstr "Anzeigename geändert."
#: uffd/views/selfservice.py:35
msgid "Display name is not valid."
msgstr "Anzeigename ist nicht valide."
#: uffd/views/selfservice.py:47
msgid "Password changed"
msgstr "Passwort geändert"
#: uffd/views/selfservice.py:64
#, python-format
msgid ""
"We received too many password reset requests for this user! Please wait "
"at least %(delay)s."
msgstr ""
"Wir haben zu viele fehlgeschlagene Anmeldeversuche für diesen Account! "
"Bitte warte mindestens %(delay)s."
#: uffd/views/selfservice.py:70
msgid ""
"We sent a mail to this user's mail address if you entered the correct "
"mail and login name combination"
msgstr ""
"Falls E-Mail-Adresse und Anmeldename richtig waren, wurde eine E-Mail an "
"die Adresse gesendet."
#: uffd/views/selfservice.py:87 uffd/views/selfservice.py:138
#: uffd/views/selfservice.py:143
msgid "Link invalid or expired"
msgstr "Link ist ungültig oder abgelaufen"
#: uffd/views/selfservice.py:92
msgid "You need to set a password, please try again."
msgstr "Password fehlt, bitte versuche es erneut."
#: uffd/views/selfservice.py:95
msgid "Passwords do not match, please try again."
msgstr "Die Passwörter stimmen nicht überein, bitte versuche es erneut"
#: uffd/views/selfservice.py:100
msgid "Password ist not valid, please try again."
msgstr "Ungültiges Passwort, bitte versuche es erneut"
#: uffd/views/selfservice.py:104
msgid "New password set"
msgstr "Passwort geändert"
#: uffd/views/selfservice.py:117
msgid "E-Mail address already exists"
msgstr "E-Mail-Adresse existiert bereits"
#: uffd/views/selfservice.py:124 uffd/views/selfservice.py:162
#: uffd/views/selfservice.py:237
#, python-format
msgid "E-Mail to \"%(mail_address)s\" could not be sent!"
msgstr "E-Mail an \"%(mail_address)s\" konnte nicht gesendet werden!"
#: uffd/views/selfservice.py:126 uffd/views/selfservice.py:164
msgid "We sent you an email, please verify your mail address."
msgstr "Wir haben dir eine E-Mail gesendet, bitte prüfe deine E-Mail-Adresse."
#: uffd/views/selfservice.py:141
msgid ""
"This link was generated for another user. Login as the correct user to "
"continue."
msgstr ""
"Dieser Link wurde für einen anderen Account erstellt. Melde dich mit dem "
"richtigen Account an um Fortzufahren."
#: uffd/views/selfservice.py:150
msgid "E-Mail address is already used by another account"
msgstr "E-Mail-Adresse wird bereits von einem anderen Account verwendet"
#: uffd/views/selfservice.py:152
msgid "E-Mail address verified"
msgstr "E-Mail-Adresse verifiziert"
#: uffd/views/selfservice.py:177
msgid "E-Mail address deleted"
msgstr "E-Mail-Adresse gelöscht"
#: uffd/views/selfservice.py:198
msgid "E-Mail preferences updated"
msgstr "E-Mail-Einstellungen geändert"
#: uffd/views/selfservice.py:208
msgid "Session revoked"
msgstr "Sitzung widerrufen"
#: uffd/views/selfservice.py:219
#, python-format
msgid "You left role %(role_name)s"
msgstr "Rolle %(role_name)s verlassen"
#: uffd/views/service.py:34
msgid "Services"
msgstr "Dienste"
#: uffd/views/session.py:84
#, python-format
msgid ""
"We received too many invalid login attempts for this user! Please wait at"
" least %(delay)s."
msgstr ""
"Wir haben zu viele fehlgeschlagene Anmeldeversuche für diesen Account "
"erhalten! Bitte warte mindestens %(delay)s."
#: uffd/views/session.py:93
msgid "Login name or password is wrong"
msgstr "Der Anmeldename oder das Passwort ist falsch"
#: uffd/views/session.py:96
#, python-format
msgid "Your account is deactivated. Contact %(contact_email)s for details."
msgstr ""
"Dein Account ist deaktiviert. Kontaktiere %(contact_email)s für weitere "
"Informationen."
#: uffd/views/session.py:102
msgid "You do not have access to this service"
msgstr "Du hast keinen Zugriff auf diesen Service"
#: uffd/views/session.py:114 uffd/views/session.py:125
msgid "You need to login first"
msgstr "Du musst dich erst anmelden"
#: uffd/views/session.py:146 uffd/views/session.py:156
msgid "Initiation code is no longer valid"
msgstr "Startcode ist nicht mehr gültig"
#: uffd/views/session.py:160
msgid "Invalid confirmation code"
msgstr "Ungültiger Bestätigungscode"
#: uffd/views/session.py:172 uffd/views/session.py:183
msgid "Invalid initiation code"
msgstr "Ungültiger Startcode"
#: uffd/views/signup.py:21
msgid "Signup not enabled"
msgstr "Account-Registrierung ist deaktiviert"
#: uffd/views/signup.py:84 uffd/views/signup.py:92
msgid "Invalid signup link"
msgstr "Ungültiger Account-Registrierungs-Link"
#: uffd/views/signup.py:97
#, python-format
msgid "Too many failed attempts! Please wait %(delay)s."
msgstr "Zu viele fehlgeschlagene Versuche! Bitte warte mindestens %(delay)s."
#: uffd/views/signup.py:113
msgid "Your account was successfully created"
msgstr "Account erfolgreich erstellt"
#: uffd/views/user.py:30
msgid "Users"
msgstr "Accounts"
#: uffd/views/user.py:48
msgid "Login name does not meet requirements"
msgstr "Anmeldename entspricht nicht den Anforderungen"
#: uffd/views/user.py:55 uffd/views/user.py:129
msgid "Display name does not meet requirements"
msgstr "Anzeigename entspricht nicht den Anforderungen"
#: uffd/views/user.py:74
msgid "Service user created"
msgstr "Service-Account erstellt"
#: uffd/views/user.py:77
msgid "User created. We sent the user a password reset link by e-mail"
msgstr ""
"Account erstellt. E-Mail mit einem Link zum Setzen des Passworts wurde "
"versendet."
#: uffd/views/user.py:106
msgid "E-Mail address already exists or is used by another account"
msgstr ""
"E-Mail-Adresse existiert bereits oder wird von einem anderen Account "
"verwendet"
#: uffd/views/user.py:134
msgid "Password is invalid"
msgstr "Passwort ist ungültig"
#: uffd/views/user.py:146
msgid "User updated"
msgstr "Account aktualisiert"
#: uffd/views/user.py:155
msgid "User deactivated"
msgstr "Account deaktiviert"
#: uffd/views/user.py:164
msgid "User activated"
msgstr "Account aktiviert"
#: uffd/views/user.py:173
msgid "Sessions revoked"
msgstr "Sitzungen widerrufen"
#: uffd/views/user.py:183
msgid "Deleted user"
msgstr "Account gelöscht"
from .views_user import bp as bp_user
from .views_group import bp as bp_group
bp = [bp_user, bp_group]
import secrets
import string
from flask import current_app
from ldap3.utils.hashed import hashed, HASHED_SALTED_SHA512
from uffd.ldap import ldap
from uffd.lazyconfig import lazyconfig_str, lazyconfig_list
def get_next_uid():
max_uid = current_app.config['LDAP_USER_MIN_UID']
for user in User.query.all():
if user.uid <= current_app.config['LDAP_USER_MAX_UID']:
max_uid = max(user.uid, max_uid)
next_uid = max_uid + 1
if next_uid > current_app.config['LDAP_USER_MAX_UID']:
raise Exception('No free uid found')
return next_uid
class ObjectAttributeDict:
def __init__(self, obj):
self.obj = obj
def __getitem__(self, key):
return getattr(self.obj, key)
def format_with_attributes(fmtstr, obj):
# Do str.format-style string formatting with the attributes of an object
# E.g. format_with_attributes("/home/{loginname}", obj) = "/home/foobar" if obj.loginname = "foobar"
return fmtstr.format_map(ObjectAttributeDict(obj))
class BaseUser(ldap.Model):
ldap_search_base = lazyconfig_str('LDAP_USER_SEARCH_BASE')
ldap_filter_params = lazyconfig_list('LDAP_USER_SEARCH_FILTER')
ldap_object_classes = lazyconfig_list('LDAP_USER_OBJECTCLASSES')
ldap_dn_base = lazyconfig_str('LDAP_USER_SEARCH_BASE')
ldap_dn_attribute = lazyconfig_str('LDAP_USER_DN_ATTRIBUTE')
uid = ldap.Attribute(lazyconfig_str('LDAP_USER_UID_ATTRIBUTE'), default=get_next_uid, aliases=lazyconfig_list('LDAP_USER_UID_ALIASES'))
loginname = ldap.Attribute(lazyconfig_str('LDAP_USER_LOGINNAME_ATTRIBUTE'), aliases=lazyconfig_list('LDAP_USER_LOGINNAME_ALIASES'))
displayname = ldap.Attribute(lazyconfig_str('LDAP_USER_DISPLAYNAME_ATTRIBUTE'), aliases=lazyconfig_list('LDAP_USER_DISPLAYNAME_ALIASES'))
mail = ldap.Attribute(lazyconfig_str('LDAP_USER_MAIL_ATTRIBUTE'), aliases=lazyconfig_list('LDAP_USER_MAIL_ALIASES'))
pwhash = ldap.Attribute('userPassword', default=lambda: hashed(HASHED_SALTED_SHA512, secrets.token_hex(128)))
groups = set() # Shuts up pylint, overwritten by back-reference
roles = set() # Shuts up pylint, overwritten by back-reference
def add_default_attributes(self):
for name, values in current_app.config['LDAP_USER_DEFAULT_ATTRIBUTES'].items():
if self.ldap_object.getattr(name):
continue
if not isinstance(values, list):
values = [values]
formatted_values = []
for value in values:
if isinstance(value, str):
value = format_with_attributes(value, self)
formatted_values.append(value)
self.ldap_object.setattr(name, formatted_values)
ldap_add_hooks = ldap.Model.ldap_add_hooks + (add_default_attributes,)
# Write-only property
def password(self, value):
self.pwhash = hashed(HASHED_SALTED_SHA512, value)
password = property(fset=password)
def is_in_group(self, name):
if not name:
return True
for group in self.groups:
if group.name == name:
return True
return False
def has_permission(self, required_group=None):
if not required_group:
return True
group_names = {group.name for group in self.groups}
group_sets = required_group
if isinstance(group_sets, str):
group_sets = [group_sets]
for group_set in group_sets:
if isinstance(group_set, str):
group_set = [group_set]
if set(group_set) - group_names == set():
return True
return False
def set_loginname(self, value):
if len(value) > 32 or len(value) < 1:
return False
for char in value:
if not char in string.ascii_lowercase + string.digits + '_-':
return False
self.loginname = value
return True
def set_displayname(self, value):
if len(value) > 128 or len(value) < 1:
return False
self.displayname = value
return True
def set_password(self, value):
if len(value) < 8 or len(value) > 256:
return False
self.password = value
return True
def set_mail(self, value):
if len(value) < 3 or '@' not in value:
return False
self.mail = value
return True
User = BaseUser
class Group(ldap.Model):
ldap_search_base = lazyconfig_str('LDAP_GROUP_SEARCH_BASE')
ldap_filter_params = lazyconfig_list('LDAP_GROUP_SEARCH_FILTER')
gid = ldap.Attribute(lazyconfig_str('LDAP_GROUP_GID_ATTRIBUTE'))
name = ldap.Attribute(lazyconfig_str('LDAP_GROUP_NAME_ATTRIBUTE'))
description = ldap.Attribute(lazyconfig_str('LDAP_GROUP_DESCRIPTION_ATTRIBUTE'), default='')
members = ldap.Relationship(lazyconfig_str('LDAP_GROUP_MEMBER_ATTRIBUTE'), User, backref='groups')
roles = [] # Shuts up pylint, overwritten by back-reference
{% extends 'base.html' %}
{% block body %}
<form action="{{ url_for("group.show", gid=group.gid) }}" method="POST">
<div class="align-self-center">
<div class="form-group col">
<label for="group-gid">gid</label>
<input type="number" class="form-control" id="group-gid" name="gid" value="{{ group.gid }}" readonly>
</div>
<div class="form-group col">
<label for="group-loginname">name</label>
<input type="text" class="form-control" id="group-loginname" name="loginname" value="{{ group.name }}" readonly>
</div>
<div class="col">
<span>Members:</span>
<ul class="row">
{% for member in group.members %}
<li class="col-12 col-xs-6 col-sm-4 col-md-3 col-lg-2"><a href="{{ url_for("user.show", uid=member.uid) }}">{{ member.loginname }}</a></li>
{% endfor %}
</ul>
</div>
</div>
</form>
{% endblock %}
{% extends 'base.html' %}
{% block body %}
<form action="{{ url_for("user.update", uid=user.uid) }}" method="POST">
<div class="align-self-center">
<div class="float-sm-right pb-2">
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Save</button>
<a href="{{ url_for("user.index") }}" class="btn btn-secondary">Cancel</a>
{% if user.uid %}
<a href="{{ url_for("mfa.admin_disable", uid=user.uid) }}" class="btn btn-secondary">Reset 2FA</a>
<a href="{{ url_for("user.delete", uid=user.uid) }}" onClick="return confirm('Are you sure?');" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% else %}
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
{% endif %}
</div>
<ul class="nav nav-tabs pt-2 border-0" id="tablist" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="true">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" id="roles-tab" data-toggle="tab" href="#roles" role="tab" aria-controls="roles" aria-selected="false">Roles</a>
</li>
<li class="nav-item">
<a class="nav-link" id="ldif-tab" data-toggle="tab" href="#ldif" role="tab" aria-controls="ldif" aria-selected="false">LDIF</a>
</li>
</ul>
<div class="tab-content border mb-2 pt-2" id="tabcontent">
<div class="tab-pane fade show active" id="profile" role="tabpanel" aria-labelledby="roles-tab">
<div class="form-group col">
<label for="user-uid">uid</label>
{% if user.uid %}
<input type="number" class="form-control" id="user-uid" name="uid" value="{{ user.uid }}" readonly>
{% else %}
<input type="text" class="form-control" id="user-uid" name="uid" placeholder="will be choosen" readonly>
{% endif %}
</div>
<div class="form-group col">
<label for="user-loginname">Login Name</label>
<input type="text" class="form-control" id="user-loginname" name="loginname" value="{{ user.loginname }}" {% if user.uid %}readonly{% endif %}>
<small class="form-text text-muted">
Only letters, numbers and underscore ("_") are allowed. At most 32, at least 2 characters. There is a word blacklist. Musst be unique.
</small>
</div>
<div class="form-group col">
<label for="user-loginname">Display Name</label>
<input type="text" class="form-control" id="user-displayname" name="displayname" value="{{ user.displayname }}">
<small class="form-text text-muted">
If you leave this empty it will be set to the login name. At most 128, at least 2 characters. No character restrictions.
</small>
</div>
<div class="form-group col">
<label for="user-mail">Mail</label>
<input type="email" class="form-control" id="user-mail" name="mail" value="{{ user.mail }}">
<small class="form-text text-muted">
Do a sanity check here. A user can take over another account if both have the same mail address set.
</small>
</div>
<div class="form-group col">
<label for="user-loginname">Password</label>
{% if user.uid %}
<input type="password" class="form-control" id="user-password" name="password" placeholder="●●●●●●●●">
{% else %}
<input type="password" class="form-control" id="user-password" name="password" placeholder="mail to set it will be sent" readonly>
{% endif %}
<small class="form-text text-muted">
At least 8 and at most 256 characters, no other special requirements. But please don't be stupid, do use a password manager.
</small>
</div>
</div>
<div class="tab-pane fade" id="roles" role="tabpanel" aria-labelledby="roles-tab">
<div class="form-group col">
<span>Roles:</span>
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">name</th>
<th scope="col">description</th>
</tr>
</thead>
<tbody>
{% for role in roles|sort(attribute="name") %}
<tr id="role-{{ role.id }}">
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="role-{{ role.id }}-checkbox" name="role-{{ role.id }}" value="1" aria-label="enabled"
{% if user in role.members or role.name in config["ROLES_BASEROLES"] %}checked {% endif %}
{% if role.name in config["ROLES_BASEROLES"] %}disabled {% endif %}>
</div>
</td>
<td>
<a href="{{ url_for("role.show", roleid=role.id) }}">
{{ role.name }}
</a>
</td>
<td>
{{ role.description }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="form-group col">
<span>Resulting groups (only updated after save):</span>
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">name</th>
<th scope="col">description</th>
</tr>
</thead>
<tbody>
{% for group in user.groups|sort(attribute="name") %}
<tr id="group-{{ group.gid }}">
<td>
<a href="{{ url_for("group.show", gid=group.gid) }}">
{{ group.name }}
</a>
</td>
<td>
{{ group.description }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade" id="ldif" role="tabpanel" aria-labelledby="ldif-tab">
<div class="form-group col">
<pre>{{ '<not available>'|e }}</pre>
</div>
</div>
</div>
</div>
</form>
{% endblock %}
from flask import Blueprint, render_template, url_for, redirect, flash, current_app
from uffd.navbar import register_navbar
from uffd.session import login_required, is_valid_session, get_current_user
from .models import Group
bp = Blueprint("group", __name__, template_folder='templates', url_prefix='/group/')
@bp.before_request
@login_required()
def group_acl(): #pylint: disable=inconsistent-return-statements
if not group_acl_check():
flash('Access denied')
return redirect(url_for('index'))
def group_acl_check():
return is_valid_session() and get_current_user().is_in_group(current_app.config['ACL_ADMIN_GROUP'])
@bp.route("/")
@register_navbar('Groups', icon='layer-group', blueprint=bp, visible=group_acl_check)
def index():
return render_template('group_list.html', groups=Group.query.all())
@bp.route("/<int:gid>")
def show(gid):
return render_template('group.html', group=Group.query.filter_by(gid=gid).first_or_404())