ppss-auth 0.11.1.1__tar.gz → 0.12.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. {ppss_auth-0.11.1.1/ppss_auth.egg-info → ppss_auth-0.12.0.0}/PKG-INFO +3 -1
  2. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/changelog.txt +7 -0
  3. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/__init__.py +15 -13
  4. ppss_auth-0.12.0.0/ppss_auth/alembic/versions/20260109_000ec266dd6e_add_salt.py +32 -0
  5. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/constants.py +49 -8
  6. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/models.py +109 -161
  7. ppss_auth-0.12.0.0/ppss_auth/ppss_auth_static/edituser.js +65 -0
  8. ppss_auth-0.12.0.0/ppss_auth/ppss_auth_static/ppssauth.css +26 -0
  9. ppss_auth-0.12.0.0/ppss_auth/ppss_auth_static/ppssauth.js +223 -0
  10. ppss_auth-0.12.0.0/ppss_auth/ppss_auth_utils/createdb.py +129 -0
  11. ppss_auth-0.12.0.0/ppss_auth/ppss_auth_utils/otp.py +48 -0
  12. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/ppss_auth_utils/password.py +7 -3
  13. ppss_auth-0.12.0.0/ppss_auth/ppss_auth_utils/usersession.py +70 -0
  14. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/routes.py +26 -17
  15. ppss_auth-0.12.0.0/ppss_auth/templates/change.jinja2 +31 -0
  16. ppss_auth-0.12.0.0/ppss_auth/templates/change.mako +25 -0
  17. ppss_auth-0.12.0.0/ppss_auth/templates/confirm_email.jinja2 +24 -0
  18. ppss_auth-0.12.0.0/ppss_auth/templates/confirm_email.mako +22 -0
  19. ppss_auth-0.12.0.0/ppss_auth/templates/edit2fa.jinja2 +48 -0
  20. ppss_auth-0.12.0.0/ppss_auth/templates/edit2fa.mako +44 -0
  21. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/editgroup.jinja2 +21 -21
  22. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/editgroup.mako +21 -21
  23. ppss_auth-0.12.0.0/ppss_auth/templates/editperm.jinja2 +15 -0
  24. ppss_auth-0.12.0.0/ppss_auth/templates/editperm.mako +12 -0
  25. ppss_auth-0.12.0.0/ppss_auth/templates/edituser.jinja2 +102 -0
  26. ppss_auth-0.12.0.0/ppss_auth/templates/edituser.mako +145 -0
  27. ppss_auth-0.12.0.0/ppss_auth/templates/enable2fa.jinja2 +29 -0
  28. ppss_auth-0.12.0.0/ppss_auth/templates/enable2fa.mako +27 -0
  29. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/layouts/masterlayout.jinja2 +1 -3
  30. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/layouts/masterlayout.mako +1 -3
  31. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/layouts/midlayout.jinja2 +1 -1
  32. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/layouts/midlayout.mako +1 -3
  33. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/layouts/public.jinja2 +3 -3
  34. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/layouts/public.mako +3 -3
  35. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/listgroup.jinja2 +3 -3
  36. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/listgroup.mako +3 -3
  37. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/listperm.jinja2 +3 -3
  38. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/listperm.mako +3 -3
  39. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/listuser.jinja2 +16 -5
  40. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/listuser.mako +16 -5
  41. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/login.jinja2 +14 -10
  42. ppss_auth-0.12.0.0/ppss_auth/templates/login.mako +44 -0
  43. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/login_nogrid.jinja2 +5 -5
  44. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/login_nogrid.mako +5 -5
  45. ppss_auth-0.12.0.0/ppss_auth/templates/partials/auth-apps.html +38 -0
  46. ppss_auth-0.12.0.0/ppss_auth/templates/partials/otp.html +50 -0
  47. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/recover.jinja2 +9 -9
  48. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/recover.mako +9 -9
  49. ppss_auth-0.12.0.0/ppss_auth/templates/registeruser.jinja2 +31 -0
  50. ppss_auth-0.12.0.0/ppss_auth/templates/registeruser.mako +28 -0
  51. ppss_auth-0.12.0.0/ppss_auth/templates/reset.jinja2 +43 -0
  52. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/reset.mako +14 -10
  53. ppss_auth-0.12.0.0/ppss_auth/templates/shared/splash.html +27 -0
  54. ppss_auth-0.12.0.0/ppss_auth/templates/splash.jinja2 +1 -0
  55. ppss_auth-0.12.0.0/ppss_auth/templates/splash.mako +1 -0
  56. ppss_auth-0.12.0.0/ppss_auth/templates/verify2fa.jinja2 +21 -0
  57. ppss_auth-0.12.0.0/ppss_auth/templates/verify2fa.mako +19 -0
  58. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/views/auth.py +266 -399
  59. ppss_auth-0.12.0.0/ppss_auth/views/crud.py +593 -0
  60. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0/ppss_auth.egg-info}/PKG-INFO +3 -1
  61. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth.egg-info/SOURCES.txt +14 -2
  62. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth.egg-info/requires.txt +2 -0
  63. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/setup.py +4 -2
  64. ppss_auth-0.11.1.1/ppss_auth/ppss_auth_static/ppssauth.css +0 -51
  65. ppss_auth-0.11.1.1/ppss_auth/ppss_auth_static/ppssauth.js +0 -181
  66. ppss_auth-0.11.1.1/ppss_auth/templates/change.jinja2 +0 -28
  67. ppss_auth-0.11.1.1/ppss_auth/templates/change.mako +0 -22
  68. ppss_auth-0.11.1.1/ppss_auth/templates/confirm_email.jinja2 +0 -16
  69. ppss_auth-0.11.1.1/ppss_auth/templates/confirm_email.mako +0 -9
  70. ppss_auth-0.11.1.1/ppss_auth/templates/editperm.jinja2 +0 -15
  71. ppss_auth-0.11.1.1/ppss_auth/templates/editperm.mako +0 -12
  72. ppss_auth-0.11.1.1/ppss_auth/templates/edituser.jinja2 +0 -67
  73. ppss_auth-0.11.1.1/ppss_auth/templates/edituser.mako +0 -66
  74. ppss_auth-0.11.1.1/ppss_auth/templates/enable2fa.jinja2 +0 -25
  75. ppss_auth-0.11.1.1/ppss_auth/templates/enable2fa.mako +0 -23
  76. ppss_auth-0.11.1.1/ppss_auth/templates/login.mako +0 -35
  77. ppss_auth-0.11.1.1/ppss_auth/templates/registeruser.jinja2 +0 -31
  78. ppss_auth-0.11.1.1/ppss_auth/templates/registeruser.mako +0 -28
  79. ppss_auth-0.11.1.1/ppss_auth/templates/reset.jinja2 +0 -39
  80. ppss_auth-0.11.1.1/ppss_auth/templates/shared/bootstrapcss.html +0 -1
  81. ppss_auth-0.11.1.1/ppss_auth/templates/verify2fa.jinja2 +0 -24
  82. ppss_auth-0.11.1.1/ppss_auth/templates/verify2fa.mako +0 -22
  83. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/MANIFEST.in +0 -0
  84. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/README.md +0 -0
  85. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/babel.ini +0 -0
  86. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/alembic.ini +0 -0
  87. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/env.py +0 -0
  88. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/script.py.mako +0 -0
  89. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/20201026_52f4d4eefdfe_unique_permission_name.py +0 -0
  90. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/20201120_6c9e84ab8280_unique_user_name.py +0 -0
  91. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/20220329_44fde88fa438_creazione_colonne_per_login_history.py +0 -0
  92. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/20220331_6190d529682d_result_reason.py +0 -0
  93. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/20231016_3f4032dd2d1b_added_email_to_user.py +0 -0
  94. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/20240110_4b95deb2d91c_tabella_reset_token.py +0 -0
  95. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/20240209_7d2e85bbf831_add_otp_hash.py +0 -0
  96. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/20240304_483944c8ff05_add_phone.py +0 -0
  97. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/alembic/versions/f724fac25359_creation.py +0 -0
  98. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/locale/en/LC_MESSAGES/ppss_auth.mo +0 -0
  99. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/locale/fr/LC_MESSAGES/ppss_auth.mo +0 -0
  100. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/locale/it/LC_MESSAGES/ppss_auth.mo +0 -0
  101. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/ppss_auth_static/loader.js +0 -0
  102. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/ppss_auth_static/template.html +0 -0
  103. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/ppss_auth_utils/__init__.py +0 -0
  104. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/ppss_auth_utils/db.py +0 -0
  105. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/ppss_auth_utils/emailclient.py +0 -0
  106. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/ppss_auth_utils/i18n.py +0 -0
  107. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/ppss_auth_utils/scriptutils.py +0 -0
  108. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/scripts/__init__.py +0 -0
  109. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/scripts/alchemyutils.py +0 -0
  110. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/scripts/cleanup_db.py +0 -0
  111. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/scripts/create_user.py +0 -0
  112. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/scripts/initialize_db.py +0 -0
  113. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/scripts/sayauth.py +0 -0
  114. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/scripts/upgrade_db.py +0 -0
  115. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/email/activation.jinja2 +0 -0
  116. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/email/activation.mako +0 -0
  117. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/email/recover.jinja2 +0 -0
  118. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/email/recover.mako +0 -0
  119. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/partials/userblock.jinja2 +0 -0
  120. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/partials/userblock.mako +0 -0
  121. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/partials/usermanage.jinja2 +0 -0
  122. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/templates/partials/usermanage.mako +0 -0
  123. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth/views/__init__.py +0 -0
  124. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth.egg-info/dependency_links.txt +0 -0
  125. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth.egg-info/entry_points.txt +0 -0
  126. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/ppss_auth.egg-info/top_level.txt +0 -0
  127. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/readme.txt +0 -0
  128. {ppss_auth-0.11.1.1 → ppss_auth-0.12.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ppss_auth
3
- Version: 0.11.1.1
3
+ Version: 0.12.0.0
4
4
  Summary: simple auth scheme for pyramid, based on Mako template and sqlalchemy backend
5
5
  Home-page: https://bitbucket.org/pingpongstars/ppss_auth/src/master/
6
6
  Author: pdepmcp
@@ -23,6 +23,8 @@ Requires-Dist: alembic
23
23
  Requires-Dist: cryptography
24
24
  Requires-Dist: pyotp
25
25
  Requires-Dist: qrcode
26
+ Requires-Dist: Pillow
27
+ Requires-Dist: requests
26
28
 
27
29
  This package aims to give and easy pluggable module to provide authentication and user maintennance in a Pyramid web application.
28
30
  It relies the Pyramid+SQLAlchemy+Mako stack. Implementation for other template languages is on the roadmap.
@@ -1,3 +1,10 @@
1
+ ## v0.12
2
+ - recover password
3
+ - session time limit
4
+ - salt and pepper for password hashing
5
+ - change email
6
+ - BO user page UX and UI
7
+
1
8
  ## v0.11.1.1
2
9
  - fix js groups edit page
3
10
 
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- #from views.auth import ppssauthpolicy,ACLRoot,getPrincipals
3
+ from ppss_auth.ppss_auth_utils.usersession import UserSession
4
4
  from sqlalchemy import engine_from_config
5
5
  from sqlalchemy.orm import sessionmaker
6
6
  import transaction
@@ -12,10 +12,11 @@ from pyramid.authorization import ACLAuthorizationPolicy
12
12
  from pyramid.authentication import SessionAuthenticationPolicy
13
13
  from pyramid.threadlocal import get_current_request
14
14
  from .constants import Conf
15
- from .models import initdb,PPSsuser
15
+ from .models import PPSsuser
16
+
16
17
  from .routes import configRoutes
17
18
  from .ppss_auth_utils.db import checkDBRevision
18
-
19
+ from .ppss_auth_utils.createdb import initdb
19
20
  import logging
20
21
  l = logging.getLogger('ppssauth')
21
22
 
@@ -48,21 +49,21 @@ def initAuthDb(settings):
48
49
 
49
50
  def getLoggedUser(request,addinsession=False):
50
51
  uid = request.session[Conf.sessionuser]['id'] if Conf.sessionuser in request.session else False
51
- user = request.session[Conf.sessionuser]['user'] if Conf.sessionuser in request.session else False
52
+ user = request.session[Conf.sessionuser].get('user',False) if Conf.sessionuser in request.session else False
52
53
  if user:
53
54
  if addinsession:
54
55
  request.dbsession.add(user)
55
56
  return user
56
57
  else:
57
58
  return None
58
- #l.warn("*****getting logged user for {}".format(uid))
59
- if uid:
60
- user = PPSsuser.byId(uid,request.dbsession)
61
- return user
62
- else:
63
- pass
64
- #l.warn("**** session is:{}.\nI was looking for this key:{} -> {}".format(request.session,Conf.sessionuser,request.session.get(Conf.sessionuser,None)))
65
- return None
59
+
60
+
61
+ def getUserSession(request):
62
+ if not hasattr(request,'ppss_usersession'):
63
+ user = request.loggeduser
64
+ usersession = UserSession(user,request)
65
+ request.ppss_usersession = usersession
66
+ return request.ppss_usersession
66
67
 
67
68
 
68
69
  def add_renderer_globals(event):
@@ -109,6 +110,7 @@ def includeme(config):
109
110
  settings = config.get_settings()
110
111
  Conf.setup(settings)
111
112
  config.add_request_method(getLoggedUser,'loggeduser',reify=True)
113
+ config.add_request_method(getUserSession,'usersession',reify=True)
112
114
 
113
115
  config.add_translation_dirs('ppss_auth:locale/')
114
116
 
@@ -125,7 +127,7 @@ def includeme(config):
125
127
  from .views.auth import getPrincipals,ACLRoot
126
128
  authz_policy = ACLAuthorizationPolicy()
127
129
  config.set_authentication_policy(SessionAuthenticationPolicy(callback=getPrincipals) )
128
- config.set_authorization_policy(ACLAuthorizationPolicy())
130
+ config.set_authorization_policy(authz_policy)
129
131
  config.set_root_factory(ACLRoot)
130
132
  config.scan("ppss_auth")
131
133
  pass
@@ -0,0 +1,32 @@
1
+ """add salt
2
+
3
+ Revision ID: 000ec266dd6e
4
+ Revises: 483944c8ff05
5
+ Create Date: 2026-01-09 12:17:03.593447
6
+
7
+ """
8
+ import secrets
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+ from sqlalchemy.dialects import mysql
12
+ from sqlalchemy import table, column
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = '000ec266dd6e'
16
+ down_revision = '483944c8ff05'
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ def upgrade():
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column('ppss_user', sa.Column('salt', sa.Unicode(length=64), nullable=True))
24
+ op.create_unique_constraint(op.f('uq_ppss_user_salt'), 'ppss_user', ['salt'])
25
+ # ### end Alembic commands ###
26
+
27
+
28
+ def downgrade():
29
+ # ### commands auto generated by Alembic - please adjust! ###
30
+ op.drop_constraint(op.f('uq_ppss_user_salt'), 'ppss_user', type_='unique')
31
+ op.drop_column('ppss_user', 'salt')
32
+ # ### end Alembic commands ###
@@ -15,11 +15,11 @@ else:
15
15
  return False
16
16
  return hasattr(v, '__iter__')
17
17
 
18
- def getBT(version = 4):
19
- v4 = version == 4
20
- return {
21
- 'xs': 'col-' if v4 else 'col-xs-'
22
- }
18
+ # def getBT(version = 4):
19
+ # v4 = version == 4
20
+ # return {
21
+ # 'xs': 'col-' if v4 else 'col-xs-'
22
+ # }
23
23
 
24
24
  class Conf():
25
25
 
@@ -46,6 +46,35 @@ class Conf():
46
46
  raise e
47
47
  else:
48
48
  return default
49
+
50
+
51
+ @staticmethod
52
+ def getTimeSpan(confval):
53
+ try:
54
+ if isinstance(confval, int):
55
+ return datetime.timedelta(seconds=confval)
56
+ elif isinstance(confval, str):
57
+ if confval.endswith('h'):
58
+ confval = confval[:-1]
59
+ confval = int(confval) * 3600
60
+ elif confval.endswith('m'):
61
+ confval = confval[:-1]
62
+ confval = int(confval) * 60
63
+ elif confval.endswith('s'):
64
+ confval = confval[:-1]
65
+ confval = int(confval)
66
+ confval = int(confval)
67
+ return datetime.timedelta(seconds=confval)
68
+ else:
69
+ raise ValueError("Invalid timespan value:{}".format(confval))
70
+ except Exception as e:
71
+ l.exception(u"Exception parsing timespan value:{}".format(confval))
72
+ raise e
73
+
74
+
75
+ @classmethod
76
+ def is2faConfigured(cls):
77
+ return cls.issuer2fa
49
78
 
50
79
  @classmethod
51
80
  def setup(cls, settings):
@@ -68,7 +97,6 @@ class Conf():
68
97
  cls.activationemail = settings.get("ppss_auth.register_requireactivation", "true").lower() != "false"
69
98
 
70
99
  cls.newusergroups = [x for x in map(str.strip, ( settings.get("ppss_auth.newusergroups", '' ).split(",") ) ) if x ] if settings.get("ppss_auth.newusergroups", '' ) else ['signeduser']
71
- cls.saltforhash = settings.get("ppss_auth.salt", "ImTheSaltOfThisLife")
72
100
 
73
101
  cls.forbiddentologin = settings.get("ppss_auth.forbidden_to_login", "true").lower() == 'true'
74
102
 
@@ -80,8 +108,14 @@ class Conf():
80
108
  cls.passwordminlen = int(settings.get("ppss_auth.password_min_len",12))
81
109
  cls.passwordrelist = Conf.conf2list( settings.get("ppss_auth.password_relist",["[A-Z]+","[a-z]+","[0-9]+","[!,\.\?\\/;:_\-+*@\£\$\%\&\(\)\"\'=\^àèìòùé\#§°ç{}]+",".{{{len},}}".format(len=cls.passwordminlen)]) )
82
110
  cls.forbiddenpasswordlist = settings.get("ppss_auth.forbidden_password_list","")
111
+ ##TODO:
112
+ ##add salt, pepper e session TTL
113
+ cls.sessionmaxttl = Conf.getTimeSpan(settings.get("ppss_auth.session_max_ttl","15m") )
114
+ cls.pepperforhash = settings.get("ppss_auth.pepper","")
115
+
116
+
83
117
  #cls.passwordwrongmessage = f'La nuova password deve essere differente dalle precedenti {cls.passwordpreviousdifferent} e avere almeno 12 caratteri, una maiuscola, una minuscola, un numero e un carattere fra "!,.?\\/;:_-+*".'
84
- cls.passwordwrongmessage = f'The new password must be different from the previous {cls.passwordpreviousdifferent} and should contain at least {cls.passwordminlen} chars, cotaining at least 1 upper case, 1 lower case, 1 number and a special char among these "!,.?\\/;:_-+*".'
118
+ cls.passwordwrongmessage = f'The new password must be different from the previous {cls.passwordpreviousdifferent} and should contain at least {cls.passwordminlen} chars, containing at least 1 upper case, 1 lower case, 1 number and a special char among these "!,.?\\/;:_-+*".'
85
119
  cls.passwordexpiredmessage = 'Password expired. Please change it.'
86
120
  cls.passwordexpire = int(settings.get("ppss_auth.password_expire",0) )
87
121
 
@@ -111,12 +145,14 @@ class Conf():
111
145
 
112
146
  cls.botemplateinherit = settings.get("ppss_auth.botemplateinherit", cls.tplpath("public", True) )
113
147
 
148
+ cls.splashtemplate = settings.get("ppss_auth.splashtemplate", cls.tplpath("splash") )
149
+
114
150
 
115
151
  #bo template inheritance
116
152
  cls.mastertemplateinherit = settings.get("ppss_auth.mastertemplateinherit", cls.tplpath("masterlayout", True) )
117
153
  cls.sectiontemplateinherit = settings.get("ppss_auth.sectiontemplateinherit", cls.tplpath("midlayout", True) )
118
154
 
119
- cls.bootstrapClasses = getBT(int(settings.get("ppss_auth.bootstrapversion",4)))
155
+ # cls.bootstrapClasses = getBT(int(settings.get("ppss_auth.bootstrapversion",4)))
120
156
 
121
157
  cls.testurl = settings.get("ppss_auth.testurl", "/test")
122
158
 
@@ -160,3 +196,8 @@ class Conf():
160
196
  cls.issuer2fa = settings.get("ppss_auth.issuer2fa", None)
161
197
  cls.enable2fatpl = settings.get("ppss_auth.enable2fatpl", cls.tplpath('enable2fa'))
162
198
  cls.verify2fatpl = settings.get("ppss_auth.verify2fatpl", cls.tplpath('verify2fa'))
199
+ cls.edit2fatpl = settings.get("ppss_auth.edit2fatpl", cls.tplpath('edit2fa'))
200
+ cls.hoursvalid2fa = int(settings.get("ppss_auth.hoursvalid2fa", 24))
201
+ # turnstile captcha
202
+ cls.turnstile_sitekey = settings.get("ppss_auth.turnstile_sitekey", None)
203
+ cls.turnstile_secretkey = settings.get("ppss_auth.turnstile_secretkey", None)
@@ -1,7 +1,6 @@
1
- from enum import unique
1
+ import secrets
2
2
  import sys
3
3
 
4
- import pyotp
5
4
  PY2 = sys.version_info[0] == 2
6
5
  if not PY2:
7
6
  unicode = str
@@ -31,9 +30,10 @@ NAMING_CONVENTION = {
31
30
  "pk": "pk_%(table_name)s"
32
31
  }
33
32
 
34
- import logging,uuid
33
+ import logging
35
34
 
36
35
  from sqlalchemy.orm import joinedload
36
+ from pyramid.request import Request
37
37
 
38
38
  metadata = MetaData(naming_convention=NAMING_CONVENTION)
39
39
  Base = declarative_base(metadata=metadata)
@@ -45,128 +45,6 @@ from .ppss_auth_utils.password import getPasswordDigest
45
45
  l = logging.getLogger('ppssauth.models')
46
46
 
47
47
 
48
- class constants():
49
- SYSADMINPERM = "sysadmin"
50
- SYSADMINGROUP = "sysadmin"
51
-
52
- def __createPermissions(session,permissions):
53
- permmap = {}
54
- for p in permissions:
55
- perm = session.query(PPSspermission).filter(PPSspermission.name == p['name']).first()
56
- if not perm:
57
- perm = PPSspermission(name = p['name'])
58
- session.add(perm)
59
- l.info("permission '{}' added to session".format(p['name']))
60
- perm.permtype = p['permtype'] if 'permtype' in p else 0
61
- permmap[p['name'] ] = perm
62
- return permmap
63
-
64
- def __createGroups(session,groups,permissions):
65
- groupmap = {}
66
- for g in groups:
67
- g = g.split("=")
68
- gname = str.strip(g[0])
69
- if len(g) == 2:
70
- permlist = map(str.strip,g[1].split(","))
71
- else:
72
- permlist = []
73
- group = session.query(PPSsgroup).filter(PPSsgroup.name == gname).first()
74
- if not group:
75
- group = PPSsgroup(name = gname)
76
- session.add(group)
77
- l.info("group '{}' added to session".format(gname))
78
- groupmap[gname] = group
79
- for p in permlist:
80
- if not group.hasPermission(p):
81
- if p in permissions:
82
- perm = permissions[p]
83
- else:
84
- perm = PPSspermission(name = p,permtype = 0)
85
- session.add(perm)
86
- permissions[p] = perm
87
- l.info("new permission '{}' created for group '{}'".format(p,gname))
88
- group.permissions.append(perm)
89
- return groupmap
90
-
91
- def __createUsers(session,users,groups,defaultpassword = None):
92
- usermap = {}
93
- for u in users:
94
- u = u.split("=")
95
- uname = u[0].split("/")
96
- upassword = None
97
- if len(uname)==2:
98
- upassword = uname[1].strip()
99
- else:
100
- upassword = Conf.defaultpassword
101
- uname = uname[0].strip()
102
- if len(u)==2:
103
- grouplist = map(str.strip,u[1].split(","))
104
- else:
105
- grouplist = []
106
- user = session.query(PPSsuser).filter(PPSsuser.username == uname).first()
107
- if not user:
108
- user = PPSsuser(username = uname)
109
- if upassword:
110
- user.setPassword(upassword)
111
- elif defaultpassword:
112
- user.setPassword(defaultpassword)
113
- session.add(user)
114
- l.info("user '{}' added to session".format(uname))
115
- usermap[uname] = user
116
- for g in grouplist:
117
- if not user.isInGroup(g,False):
118
- if g in groups:
119
- group = groups[g]
120
- else:
121
- group = session.query(PPSsgroup).filter(PPSsgroup.name == g).first()
122
- if group is None:
123
- group = PPSsgroup(name = g)
124
- session.add(group)
125
- else:
126
- groups[g] = group
127
- l.info("new group '{}' created for user '{}'".format(g,uname))
128
- user.groups.append(group)
129
- return usermap
130
-
131
-
132
- ##the caller must commit this
133
- def initdb(session=None,createdefault=False,engine = None):
134
- if engine is None:
135
- engine = session.get_bind()
136
- try:
137
- PPSsuser.all(session)
138
- except:
139
- raise Exception("no user table. exiting!")
140
- if createdefault:
141
- l.info("creating default")
142
- systemperms = [#PPSspermission(name = u"admin"),
143
- {"name": u"edituser","permtype": 1},
144
- {"name": u"listuser","permtype": 1},
145
- {"name": u"login" ,"permtype": 1},
146
- {"name": constants.SYSADMINPERM,"permtype": 1},
147
- ]
148
- permissions = [{'name':p,'permtype':1} for p in Conf.perm2create]
149
- for sp in systemperms:
150
- permissions.append(sp)
151
-
152
- permissionmap = __createPermissions(session,permissions)
153
- l.debug("permission map:{}".format(permissionmap))
154
- groupmap = __createGroups(session, Conf.group2create +
155
- [
156
- "useradmin=edituser,listuser,login",
157
- "signeduser=login",
158
- "{}={}".format(constants.SYSADMINGROUP,constants.SYSADMINPERM)
159
- ]
160
- , permissionmap)
161
- l.debug("group map:{}".format(groupmap))
162
- if Conf.adminname:
163
- root = __createUsers(session,[Conf.adminname+"="+constants.SYSADMINGROUP],groupmap,Conf.adminpass if Conf.adminpass else None)
164
- usermap = __createUsers(session, Conf.user2create, groupmap )
165
- l.debug("user map:{}".format(usermap))
166
- return 0
167
-
168
- return 0
169
-
170
48
  ppssuserlkppssgroup = Table('ppssuser_lk_ppssgroup', Base.metadata,
171
49
  Column('user_id',Integer,ForeignKey('ppss_user.id', ondelete="CASCADE"), primary_key=True),
172
50
  Column('group_id',Integer,ForeignKey('ppss_group.id', ondelete="CASCADE"), primary_key=True )
@@ -223,6 +101,16 @@ class commonTable():
223
101
  def firstByField(cls,field,value,DBSession):
224
102
  return DBSession.query(cls).filter(getattr(cls, field)==value).first()
225
103
 
104
+ class LOGINREASON():
105
+ OTPSKIPPED = (3,"OTP not required")
106
+ OTPPASSED = (2,"OTP Ok")
107
+ OK = (1,"Ok")
108
+ VALIDATIONERROR = (0,"invalid password")
109
+ BLOCKED = (-1,"too many recent fails")
110
+ DISABLED = (-2,"user disabled")
111
+ DONTEXIST = (-3,"user does not exist")
112
+ OTPERROR = (-4,"OTP error")
113
+
226
114
  class LoginError():
227
115
  def __init__(self,validlogin,enabled,failcount):
228
116
  self.validlogin = validlogin
@@ -230,25 +118,39 @@ class LoginError():
230
118
  self.failcount = failcount
231
119
 
232
120
  def __bool__(self):
233
- #return self.validlogin and self.enabled and self.failcount
121
+ # return self.validlogin and self.enabled and self.failcount
234
122
  return self.result() == 1
235
123
 
124
+ def __result(self):
125
+ if self.enabled is None:
126
+ return LOGINREASON.DONTEXIST
127
+ if not self.enabled:
128
+ return LOGINREASON.DISABLED
129
+ if self.failcount:
130
+ return LOGINREASON.BLOCKED
131
+ if not self.validlogin:
132
+ return LOGINREASON.VALIDATIONERROR
133
+ return LOGINREASON.OK
134
+
135
+
236
136
  def result(self):
237
- return 1 if self.failcount and self.validlogin and self.enabled else 0
137
+ return self.__result()[0]
138
+ #return 1 if self.failcount and self.validlogin and self.enabled else 0 if self.validlogin else -1 if self.failcount else -2
238
139
 
239
140
  def blocked(self):
240
141
  return self.failcount is not None and not self.failcount
241
142
 
242
143
  def resultreason(self):
243
- if self.enabled is None:
244
- return "user does not exist"
245
- if not self.enabled:
246
- return "user disabled"
247
- if not self.validlogin:
248
- return "invalid password"
249
- if not self.failcount:
250
- return "too many recent fails"
251
- return "Ok"
144
+ return self.__result()[1]
145
+ # if self.enabled is None:
146
+ # return "user does not exist"
147
+ # if not self.enabled:
148
+ # return "user disabled"
149
+ # if not self.validlogin:
150
+ # return "invalid password"
151
+ # if not self.failcount:
152
+ # return "too many recent fails"
153
+ # return "Ok"
252
154
 
253
155
  def __str__(self):
254
156
  return self.username
@@ -257,10 +159,10 @@ class PPSsuser(Base,commonTable):
257
159
  __tablename__ = 'ppss_user'
258
160
  id = Column(Integer, primary_key=True)
259
161
  username = Column(Unicode(128),unique=True)
260
-
162
+
261
163
  ###EMAIL
262
164
  email = Column(Unicode(128),unique=True)
263
-
165
+
264
166
  password = Column(Unicode(1024))
265
167
  insertdt = Column(DateTime,default=datetime.now)
266
168
  updatedt = Column(DateTime,default=datetime.now,onupdate=datetime.now)
@@ -280,67 +182,95 @@ class PPSsuser(Base,commonTable):
280
182
 
281
183
  phone_prefix = Column(Unicode(8))
282
184
  phone_number = Column(Unicode(16), unique=True)
185
+
186
+ ### add a new field salt for password hashing with a unique salt per user
187
+ salt = Column(Unicode(64), unique=True )
283
188
 
284
189
  superusercache = None
285
190
  permissionscache = None
286
191
  permissionsmapcache = None
287
192
  groupmapcache = None
193
+ invalidusersession = False
288
194
 
289
195
  @classmethod
290
196
  def checkLogin(cls,user,password,dbsession, ipaddr = None):
291
- return PPSsuser.checkCryptedLogin(user,getPasswordDigest(password),dbsession,ipaddr=ipaddr)
197
+ # Don't hash with salt here - the salt will be applied in checkCryptedLogin
198
+ # using the user-specific salt from the database
199
+ return PPSsuser.checkCryptedLogin(user,password,dbsession,ipaddr=ipaddr)
292
200
 
293
201
  @classmethod
294
202
  def checkCryptedLogin(cls,user,password,dbsession,ipaddr = None):
295
- #.filter(cls.password==password).filter(cls.enabled==1)
203
+ # .filter(cls.password==password).filter(cls.enabled==1)
296
204
  res = dbsession.query(cls).filter(cls.username==user) \
297
205
  .options(joinedload('groups')) \
298
206
  .options(joinedload('groups.permissions')) \
299
207
  .all()
300
208
  if len(res)==1:
301
209
  res:PPSsuser = res[0]
302
- failcountquery = dbsession.query(PPSsloginhistory).filter(PPSsloginhistory.user_id == res.id).filter(PPSsloginhistory.result==0).filter(PPSsloginhistory.insertdt > datetime.now()-timedelta(minutes=Conf.loginfailinterval))
210
+ # Hash the plaintext password with user's salt for comparison
211
+ password_with_salt = getPasswordDigest(password, salt=res.salt)
212
+ failcountquery = (
213
+ dbsession.query(PPSsloginhistory)
214
+ .filter(PPSsloginhistory.user_id == res.id)
215
+ .filter(PPSsloginhistory.result == LOGINREASON.VALIDATIONERROR[0])
216
+ .filter(
217
+ PPSsloginhistory.insertdt
218
+ > datetime.now() - timedelta(seconds=Conf.loginfailinterval)
219
+ )
220
+ )
303
221
  if ipaddr:
304
222
  failcountquery = failcountquery.filter(PPSsloginhistory.ipaddress == ipaddr)
305
223
  failcount = failcountquery.count()
306
- valid = LoginError(res.password == password, res.enabled == 1,failcount<Conf.loginfailthreshold)
307
- #res.password == password and res.enabled == 1
224
+ valid = LoginError(
225
+ res.password == password_with_salt,
226
+ res.enabled == 1,
227
+ failcount > Conf.loginfailthreshold,
228
+ )
229
+ # res.password == password and res.enabled == 1
308
230
  else:
309
231
  res = None
310
232
  valid = LoginError(None,None,None)
311
233
  return res,valid
312
- #return res[0] if len(res)==1 else None
234
+ # return res[0] if len(res)==1 else None
313
235
 
314
236
  def passwordExpired(self,now = None):
315
237
  if now is None:
316
238
  now = datetime.now()
317
- l.debug("user:{},now:{},expire:{},res:{}".format(self.username,now,self.passwordexpire,self.passwordexpire is None or self.passwordexpire>now))
318
- return not (self.passwordexpire is None or self.passwordexpire>now)
239
+ l.debug("-------- user:{},now:{},expire:{},res:{}".format(self.username,now,self.passwordexpire,
240
+ not ((self.passwordexpire is None) or self.passwordexpire>now)))
241
+
242
+ notexpired = ((self.passwordexpire is None) or (self.passwordexpire>now))
243
+ return not notexpired
319
244
 
320
245
  def is2faEnabled(self):
321
246
  return bool(self.otp_hash)
322
247
 
323
- def getPrincipals(self,expired=False, is2faEnabled=False):
324
- if expired:
325
- return ['ppss_auth:changepassword']
326
- elif is2faEnabled:
327
- return ['ppss_auth:otpverify']
328
- else:
329
- return [str("g:"+group.name ) for group in self.groups ]
248
+ __principals = None
249
+ def getPrincipals(self):
250
+ if self.__principals is None:
251
+ principals = [str("g:"+group.name ) for group in self.groups ]
252
+ if self.isSuperUser():
253
+ principals += ["g:admin","g:sysadmin"]
254
+ l.debug("++++ principals:{}".format(principals))
255
+ self.__principals = principals
256
+
257
+ return self.__principals,self.isSuperUser()
330
258
 
331
259
  def todict(self):
332
260
  return { "id": self.id, "username":self.username,"enabled":self.enabled}
333
261
 
334
-
335
262
  def setPassword(self,password,canexpire = True):
336
- dig = getPasswordDigest(password)
263
+ if not self.salt:
264
+ self.salt = secrets.token_hex(16)
265
+ dig = getPasswordDigest(password, salt=self.salt)
337
266
  self.password= dig
338
267
  if Conf.passwordexpire and canexpire:
339
268
  self.passwordexpire = datetime.now() + timedelta(days = Conf.passwordexpire)
269
+ else:
270
+ self.passwordexpire = None
340
271
  self.passowrdhistory.append( PPSspasswordhistory(password = dig) )
341
272
  return self
342
273
 
343
-
344
274
  def getPermissions(self):
345
275
  if self.permissionscache is None:
346
276
  result = set()
@@ -383,11 +313,24 @@ class PPSsuser(Base,commonTable):
383
313
  self.groupmapcache = set(grouplist)
384
314
  return self.groupmapcache
385
315
 
386
-
387
316
  def isInGroup(self,groupname,whitelie=True):
388
317
  if self.isSuperUser() and whitelie:return True
389
318
  return groupname in self.getGroupsMap()
390
319
 
320
+ def needVerifyOTP(self, request:Request):
321
+ since = datetime.now() - timedelta(hours=Conf.hoursvalid2fa)
322
+ # since = datetime.now() - timedelta(seconds=20)
323
+ success_login = (
324
+ request.dbsession.query(PPSsloginhistory)
325
+ .filter(PPSsloginhistory.user_id == self.id)
326
+ .filter(PPSsloginhistory.ipaddress == request.remote_addr)
327
+ .filter(PPSsloginhistory.result == LOGINREASON.OTPPASSED[0])
328
+ .filter(PPSsloginhistory.insertdt > since)
329
+ ).first()
330
+ if success_login:
331
+ return False
332
+ return True
333
+
391
334
  def __unicode__(self):
392
335
  return u"<PPSsuser ({id}-{name},{enabled})>".format(id=self.id,name=self.username, enabled=self.enabled)
393
336
 
@@ -424,6 +367,15 @@ class PPSsloginhistory(Base):
424
367
  resultreason = Column(Unicode(128))
425
368
  username = Column(Unicode(128))
426
369
 
370
+ # result int
371
+ # -3 = invalid otp
372
+ # -2 = ??
373
+ # -1 = ?? blocked ?
374
+ # 0 = failed
375
+ # 1 = success
376
+ # 2 = logged in with otp
377
+ # 3 = logged in without otp
378
+
427
379
 
428
380
  class PPSsgroup(Base,commonTable):
429
381
  __tablename__ = 'ppss_group'
@@ -501,7 +453,3 @@ class DBVersion(Base):
501
453
  else:
502
454
  version.moduleversion = moduldeversion
503
455
  version.dbversion = "1.0"
504
-
505
-
506
-
507
-