diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py --- a/rhodecode/apps/_base/__init__.py +++ b/rhodecode/apps/_base/__init__.py @@ -176,9 +176,6 @@ class BaseAppView(object): if not user_obj: return - if user_obj.has_forced_2fa and user_obj.extern_type != 'rhodecode': - return - if user_obj.needs_2fa_configure and view_name != self.SETUP_2FA_VIEW: h.flash( "You are required to configure 2FA", diff --git a/rhodecode/authentication/plugins/auth_crowd.py b/rhodecode/authentication/plugins/auth_crowd.py --- a/rhodecode/authentication/plugins/auth_crowd.py +++ b/rhodecode/authentication/plugins/auth_crowd.py @@ -31,7 +31,7 @@ import urllib.parse from rhodecode.translation import _ from rhodecode.authentication.base import ( RhodeCodeExternalAuthPlugin, hybrid_property) -from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase +from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase, TwoFactorAuthnPluginSettingsSchemaMixin from rhodecode.authentication.routes import AuthnPluginResourceBase from rhodecode.lib.colander_utils import strip_whitespace from rhodecode.lib.ext_json import json, formatted_json @@ -53,7 +53,7 @@ class CrowdAuthnResource(AuthnPluginReso pass -class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase): +class CrowdSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase): host = colander.SchemaNode( colander.String(), default='127.0.0.1', diff --git a/rhodecode/authentication/plugins/auth_jasig_cas.py b/rhodecode/authentication/plugins/auth_jasig_cas.py --- a/rhodecode/authentication/plugins/auth_jasig_cas.py +++ b/rhodecode/authentication/plugins/auth_jasig_cas.py @@ -33,7 +33,7 @@ import urllib.error from rhodecode.translation import _ from rhodecode.authentication.base import ( RhodeCodeExternalAuthPlugin, hybrid_property) -from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase +from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase, TwoFactorAuthnPluginSettingsSchemaMixin from rhodecode.authentication.routes import AuthnPluginResourceBase from rhodecode.lib.colander_utils import strip_whitespace from rhodecode.model.db import User @@ -55,7 +55,7 @@ class JasigCasAuthnResource(AuthnPluginR pass -class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase): +class JasigCasSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase): service_url = colander.SchemaNode( colander.String(), default='https://domain.com/cas/v1/tickets', diff --git a/rhodecode/authentication/plugins/auth_ldap.py b/rhodecode/authentication/plugins/auth_ldap.py --- a/rhodecode/authentication/plugins/auth_ldap.py +++ b/rhodecode/authentication/plugins/auth_ldap.py @@ -27,7 +27,7 @@ import colander from rhodecode.translation import _ from rhodecode.authentication.base import ( RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property) -from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase +from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase, TwoFactorAuthnPluginSettingsSchemaMixin from rhodecode.authentication.routes import AuthnPluginResourceBase from rhodecode.lib.colander_utils import strip_whitespace from rhodecode.lib.exceptions import ( @@ -245,7 +245,7 @@ class AuthLdap(AuthLdapBase): return dn, user_attrs -class LdapSettingsSchema(AuthnPluginSettingsSchemaBase): +class LdapSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase): tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS'] tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD'] search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE'] diff --git a/rhodecode/authentication/plugins/auth_pam.py b/rhodecode/authentication/plugins/auth_pam.py --- a/rhodecode/authentication/plugins/auth_pam.py +++ b/rhodecode/authentication/plugins/auth_pam.py @@ -31,7 +31,7 @@ import socket from rhodecode.translation import _ from rhodecode.authentication.base import ( RhodeCodeExternalAuthPlugin, hybrid_property) -from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase +from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase, TwoFactorAuthnPluginSettingsSchemaMixin from rhodecode.authentication.routes import AuthnPluginResourceBase from rhodecode.lib.colander_utils import strip_whitespace @@ -51,7 +51,7 @@ class PamAuthnResource(AuthnPluginResour pass -class PamSettingsSchema(AuthnPluginSettingsSchemaBase): +class PamSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase): service = colander.SchemaNode( colander.String(), default='login', diff --git a/rhodecode/authentication/plugins/auth_rhodecode.py b/rhodecode/authentication/plugins/auth_rhodecode.py --- a/rhodecode/authentication/plugins/auth_rhodecode.py +++ b/rhodecode/authentication/plugins/auth_rhodecode.py @@ -27,7 +27,7 @@ import colander from rhodecode.translation import _ from rhodecode.lib.utils2 import safe_bytes from rhodecode.model.db import User -from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase +from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase, TwoFactorAuthnPluginSettingsSchemaMixin from rhodecode.authentication.base import ( RhodeCodeAuthPluginBase, hybrid_property, HTTP_TYPE, VCS_TYPE) from rhodecode.authentication.routes import AuthnPluginResourceBase @@ -182,16 +182,7 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP return None -class RhodeCodeSettingsSchema(AuthnPluginSettingsSchemaBase): - global_2fa = colander.SchemaNode( - colander.Bool(), - default=False, - description=_('Force all users to use two factor authentication by enabling this.'), - missing=False, - title=_('Global 2FA'), - widget='bool', - ) - +class RhodeCodeSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase): auth_restriction_choices = [ (RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE, 'All users'), (RhodeCodeAuthPlugin.AUTH_RESTRICTION_SUPER_ADMIN, 'Super admins only'), diff --git a/rhodecode/authentication/schema.py b/rhodecode/authentication/schema.py --- a/rhodecode/authentication/schema.py +++ b/rhodecode/authentication/schema.py @@ -48,3 +48,17 @@ class AuthnPluginSettingsSchemaBase(cola validator=colander.Range(min=0, max=None), widget='int', ) + + +class TwoFactorAuthnPluginSettingsSchemaMixin(colander.MappingSchema): + """ + Mixin for extending plugins with two-factor authentication option. + """ + global_2fa = colander.SchemaNode( + colander.Bool(), + default=False, + description=_('Force all users to use two factor authentication with this plugin.'), + missing=False, + title=_('enforce 2FA for users'), + widget='bool', + ) diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -810,11 +810,10 @@ class User(Base, BaseModel): @hybrid_property def has_forced_2fa(self): """ - Checks if 2fa was forced for ALL users (including current one) + Checks if 2fa was forced for current user """ from rhodecode.model.settings import SettingsModel - # So now we're supporting only auth_rhodecode_global_2fa - if value := SettingsModel().get_setting_by_name('auth_rhodecode_global_2fa'): + if value := SettingsModel().get_setting_by_name(f'{self.extern_type}_global_2fa'): return value.app_settings_value return False diff --git a/rhodecode/templates/admin/auth/plugin_settings.mako b/rhodecode/templates/admin/auth/plugin_settings.mako --- a/rhodecode/templates/admin/auth/plugin_settings.mako +++ b/rhodecode/templates/admin/auth/plugin_settings.mako @@ -63,7 +63,12 @@ %elif node.widget == "password": ${h.password(node.name, defaults.get(node.name), class_="large")} %elif node.widget == "bool": -
${h.checkbox(node.name, True, checked=defaults.get(node.name))}
+ %if node.name == "global_2fa" and c.rhodecode_edition_id != "EE": + + <%node.description = _('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='sales@rhodecode.com')%> + %else: +
${h.checkbox(node.name, True, checked=defaults.get(node.name))}
+ %endif %elif node.widget == "select": ${h.select(node.name, defaults.get(node.name), node.validator.choices, class_="select2AuthSetting")} %elif node.widget == "select_with_labels": @@ -80,7 +85,7 @@ ${errors.get(node.name)}
%endif -

${node.description}

+

${node.description | n}

%endfor