feat(2fa): added 2fa for more auth plugins and moved 2fa forced functionality to ee edition. Fixes: RCCE-68

This commit is contained in:
Serhii Ilin 2024-05-13 16:24:31 +03:00
parent aea08fa1b2
commit d069311e52
9 changed files with 33 additions and 27 deletions

View file

@ -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",

View file

@ -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(AuthnPluginResourceBase):
pass
class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
class CrowdSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase):
host = colander.SchemaNode(
colander.String(),
default='127.0.0.1',

View file

@ -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(AuthnPluginResourceBase):
pass
class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
class JasigCasSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase):
service_url = colander.SchemaNode(
colander.String(),
default='https://domain.com/cas/v1/tickets',

View file

@ -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']

View file

@ -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(AuthnPluginResourceBase):
pass
class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
class PamSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase):
service = colander.SchemaNode(
colander.String(),
default='login',

View file

@ -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(RhodeCodeAuthPluginBase):
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'),

View file

@ -48,3 +48,17 @@ class AuthnPluginSettingsSchemaBase(colander.MappingSchema):
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',
)

View file

@ -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

View file

@ -63,7 +63,12 @@
%elif node.widget == "password":
${h.password(node.name, defaults.get(node.name), class_="large")}
%elif node.widget == "bool":
<div class="checkbox">${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div>
%if node.name == "global_2fa" and c.rhodecode_edition_id != "EE":
<input type="checkbox" disabled/>
<%node.description = _('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')%>
%else:
<div class="checkbox" >${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div>
%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 @@
<span class="error-message">${errors.get(node.name)}</span>
<br />
%endif
<p class="help-block pre-formatting">${node.description}</p>
<p class="help-block pre-formatting">${node.description | n}</p>
</div>
</div>
%endfor