Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
cubicweb
cubes
forgotpwd
Commits
4d32b157ca2d
Commit
60b0439a
authored
Feb 12, 2010
by
Sylvain Thénault
Browse files
3.6 api updates
parent
cdc15a307b2b
Changes
4
Hide whitespace changes
Inline
Side-by-side
__pkginfo__.py
View file @
4d32b157
...
...
@@ -14,8 +14,50 @@ http://www.logilab.fr -- mailto:contact@logilab.fr'''
author
=
'LOGILAB S.A. (Paris, FRANCE)'
author_email
=
'contact@logilab.fr'
short_desc
=
'password recovery cube'
long_desc
=
'''password recovery cube'''
short_desc
=
'password recovery component for the CubicWeb framework'
long_desc
=
"""
\
Summary
-------
The `forgotpwd` cube cube provides an easy way to generate a new
password for an user.
Depends
-------
You must use the cube `registration`.
Usage
-----
This cube creates a new entity called `Fpasswd`. This is an internal
entity: managers and users can't read/delete or modify this kink of
entity.
The workflow of password recovery is defined below :
1. ask for a new password, the user must have a valid primary email
associated to his account.
.. image:: cube_forgotpwd_1.png
2. An email has been sent. This email contains a generated url associated to an
user. This link is valid during a short period. This time limit can be
configured in the all-in-one.conf file:
.. sourcecode:: ini
[FORGOTPWD]
revocation-limit=30 # minutes
3. If the link is valid, the user can change his password in a new form.
.. image:: cube_forgotpwd_1.png
There is an automatic task that delete periodically all old Fpasswd which are
stored in the database. This task is started at the launching of the
application.
"""
web
=
'http://www.cubicweb.org/project/%s'
%
distname
...
...
hooks.py
View file @
4d32b157
""" this module contains server side hooks for cleaning forgotpwd table
"""
from
datetime
import
datetime
from
cubicweb.selectors
import
implements
from
cubicweb.server
import
hook
smanager
from
cubicweb.server
import
hook
from
cubicweb.sobjects.notification
import
NotificationView
_
=
unicode
class
ServerStartupHook
(
hook
smanager
.
Hook
):
"""
D
elete old revocation key
"""
class
ServerStartupHook
(
hook
.
Hook
):
"""
on startup, register a task to d
elete old revocation key
"""
__regid__
=
'fpwd_startup'
events
=
(
'server_startup'
,)
def
call
(
self
,
repo
):
from
datetime
import
datetime
def
cleaning_revocation_key
(
repo
):
def
__call__
(
self
):
# XXX use named args and inner functions to avoid referencing globals
# which may cause reloading pb
def
cleaning_revocation_key
(
repo
,
now
=
datetime
.
now
):
session
=
repo
.
internal_session
()
session
.
execute
(
'DELETE Fpasswd F WHERE F revocation_date < %(date)s'
,
{
'date'
:
datetime
.
now
()})
session
.
commit
()
session
.
close
()
# revocation keu must be deleted
cleaning_revocation_key
(
repo
)
try
:
session
.
execute
(
'DELETE Fpasswd F WHERE F revocation_date < %(date)s'
,
{
'date'
:
now
()})
session
.
commit
()
finally
:
session
.
close
()
# run looping task often enough to purge pwd-reset requests
limit
=
self
.
vreg
.
config
[
'revocation-limit'
]
repo
.
looping_task
(
limit
*
60
,
cleaning_revocation_key
,
repo
)
limit
=
self
.
repo
.
vreg
.
config
[
'revocation-limit'
]
*
60
self
.
repo
.
looping_task
(
limit
,
cleaning_revocation_key
,
self
.
repo
)
class
PasswordResetNotification
(
NotificationView
):
id
=
'notif_after_add_entity'
__regid__
=
'notif_after_add_entity'
__select__
=
implements
(
'Fpasswd'
)
content
=
_
(
'''There was recently a request to change the password on your account.
...
...
@@ -42,18 +47,18 @@ See you soon on %(base_url)s !
'''
)
def
subject
(
self
):
return
self
.
req
.
_
(
u
'Request to change your password'
)
return
self
.
_cw
.
_
(
u
'Request to change your password'
)
def
recipients
(
self
):
fpasswd
=
self
.
rset
.
get_entity
(
0
,
0
)
fpasswd
=
self
.
_cw
.
rset
.
get_entity
(
self
.
cw_row
or
0
,
self
.
cw_col
or
0
)
user
=
fpasswd
.
reverse_has_fpasswd
[
0
]
return
[(
user
.
get_email
(),
user
.
property_value
(
'ui.language'
))]
def
context
(
self
,
**
kwargs
):
return
{
'resetlink'
:
self
.
req
.
get_shared_data
(
'resetlink'
,
pop
=
True
),
'resetlink'
:
self
.
_cw
.
get_shared_data
(
'resetlink'
,
pop
=
True
),
# NOTE: it would probably be better to display the expiration date
# (with correct timezone)
'limit'
:
self
.
vreg
.
config
[
'revocation-limit'
],
'base_url'
:
self
.
req
.
base_url
(),
'limit'
:
self
.
_cw
.
vreg
.
config
[
'revocation-limit'
],
'base_url'
:
self
.
_cw
.
base_url
(),
}
schema.py
View file @
4d32b157
# cube's specific schema
"""
"""forgot password schema
:organization: Logilab
:copyright: 200
1
-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:copyright: 200
9
-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
from
cubicweb.schemas.base
import
CWUser
from
yams.buildobjs
import
Datetime
,
String
,
EntityType
,
RelationDefinition
from
yams.buildobjs
import
EntityType
,
Datetime
,
String
,
RelationDefinition
class
Fpasswd
(
EntityType
):
permissions
=
{
'read'
:
(
'managers'
,),
'add'
:
(),
'delete'
:
(),
'update'
:
(),
}
# Fpasswd handled by internal hooks, simply let managers removing manually
# if desired
__permissions__
=
{
'read'
:
(
'managers'
,),
'add'
:
(),
'delete'
:
(
'mamangers'
,),
'update'
:
(),
}
revocation_id
=
String
(
required
=
True
,
unique
=
True
)
revocation_date
=
Datetime
(
required
=
True
)
class
has_fpasswd
(
RelationDefinition
):
permissions
=
{
'read'
:
(
'managers'
,),
'add'
:
(),
'delete'
:
(),
}
name
=
'has_fpasswd'
__permissions__
=
{
'read'
:
(
'managers'
,),
'add'
:
(),
'delete'
:
(
'managers'
,),
}
subject
=
'CWUser'
object
=
'Fpasswd'
views.py
View file @
4d32b157
...
...
@@ -12,6 +12,8 @@ from datetime import datetime, timedelta
from
yams
import
ValidationError
from
logilab.mtconverter
import
xml_escape
from
cubicweb.web
import
(
Redirect
,
controller
,
form
,
formwidgets
as
wdg
,
formfields
as
ff
)
from
cubicweb.web.views
import
forms
,
urlrewrite
,
basetemplates
...
...
@@ -20,8 +22,6 @@ from cubicweb.server.repository import Repository
from
cubes.registration.views
import
CaptchaWidget
,
encrypt
,
decrypt
from
logilab.mtconverter
import
xml_escape
_
=
unicode
# Login form
...
...
@@ -31,20 +31,20 @@ class LogFormTemplateForgotPassword(basetemplates.LogFormTemplate):
def
login_form
(
self
,
id
):
super
(
LogFormTemplateForgotPassword
,
self
).
login_form
(
id
)
self
.
req
.
add_css
(
'cubes.forgotpwd.css'
)
self
.
_cw
.
add_css
(
'cubes.forgotpwd.css'
)
self
.
w
(
u
'<p style="text-align:center"><a href="%s">%s</a></p>'
%
(
self
.
build_url
(
'forgottenpassword'
),
self
.
req
.
_
(
'Forgot your password ?'
)))
self
.
build_url
(
'forgottenpassword'
),
self
.
_cw
.
_
(
'Forgot your password ?'
)))
# First form, send an email
# -------------------------
class
ForgottenPasswordForm
(
forms
.
FieldsForm
):
id
=
'forgottenpassword'
__regid__
=
'forgottenpassword'
form_buttons
=
[
wdg
.
SubmitButton
()]
@
property
def
action
(
self
):
return
self
.
req
.
build_url
(
u
'forgottenpassword_sendmail'
)
return
self
.
_cw
.
build_url
(
u
'forgottenpassword_sendmail'
)
use_email
=
ff
.
StringField
(
widget
=
wdg
.
TextInput
(),
required
=
True
,
label
=
_
(
u
'your email address'
))
captcha
=
ff
.
StringField
(
widget
=
CaptchaWidget
(),
required
=
True
,
...
...
@@ -52,15 +52,15 @@ class ForgottenPasswordForm(forms.FieldsForm):
help
=
_
(
'please copy the letters from the image'
))
class
ForgottenPasswordFormView
(
form
.
FormViewMixIn
,
StartupView
):
id
=
'forgottenpassword'
__regid__
=
'forgottenpassword'
def
call
(
self
):
form
=
self
.
vreg
[
'forms'
].
select
(
'forgottenpassword'
,
self
.
req
)
self
.
w
(
u
'<p>%s</p>'
%
self
.
req
.
_
(
u
'Forgot your password ?'
))
form
=
self
.
vreg
[
'forms'
].
select
(
'forgottenpassword'
,
self
.
_cw
)
self
.
w
(
u
'<p>%s</p>'
%
self
.
_cw
.
_
(
u
'Forgot your password ?'
))
self
.
w
(
form
.
render
())
class
ForgottenPasswordSendMailController
(
controller
.
Controller
):
id
=
'forgottenpassword_sendmail'
__regid__
=
'forgottenpassword_sendmail'
def
publish
(
self
,
rset
=
None
):
data
=
self
.
checked_data
()
...
...
@@ -71,72 +71,72 @@ class ForgottenPasswordSendMailController(controller.Controller):
except
Exception
,
exc
:
msg
=
str
(
exc
)
else
:
msg
=
self
.
req
.
_
(
u
'An email has been sent, follow instructions in there to change your password.'
)
msg
=
self
.
_cw
.
_
(
u
'An email has been sent, follow instructions in there to change your password.'
)
raise
Redirect
(
self
.
build_url
(
'pwdsent'
,
__message
=
msg
))
def
checked_data
(
self
):
'''only basic data check here (required attributes and password
confirmation check)
'''
fieldsform
=
self
.
vreg
[
'forms'
].
select
(
'forgottenpassword'
,
self
.
req
)
fieldsform
=
self
.
vreg
[
'forms'
].
select
(
'forgottenpassword'
,
self
.
_cw
)
data
=
{}
errors
=
{}
for
field
in
fieldsform
.
_fields_
:
value
=
self
.
req
.
form
.
get
(
field
.
name
,
u
''
).
strip
()
value
=
self
.
_cw
.
form
.
get
(
field
.
name
,
u
''
).
strip
()
if
not
value
:
if
field
.
required
:
errors
[
field
.
name
]
=
self
.
req
.
_
(
'required attribute'
)
errors
[
field
.
name
]
=
self
.
_cw
.
_
(
'required attribute'
)
data
[
field
.
name
]
=
value
captcha
=
self
.
req
.
get_session_data
(
'captcha'
,
None
,
pop
=
True
)
captcha
=
self
.
_cw
.
get_session_data
(
'captcha'
,
None
,
pop
=
True
)
if
captcha
is
None
:
errors
[
None
]
=
self
.
req
.
_
(
'unable to check captcha, please try again'
)
errors
[
None
]
=
self
.
_cw
.
_
(
'unable to check captcha, please try again'
)
elif
data
[
'captcha'
].
lower
()
!=
captcha
.
lower
():
errors
[
'captcha'
]
=
self
.
req
.
_
(
'incorrect captcha value'
)
errors
[
'captcha'
]
=
self
.
_cw
.
_
(
'incorrect captcha value'
)
if
errors
:
raise
ValidationError
(
None
,
errors
)
return
data
class
PasswordSentView
(
StartupView
):
id
=
'pwdsent'
__regid__
=
'pwdsent'
def
call
(
self
):
self
.
wview
(
'index'
,
self
.
rset
)
self
.
wview
(
'index'
,
self
.
cw_
rset
)
# Second form, ask for a new password
# -----------------------------------
class
ForgottenPasswordRequestForm
(
forms
.
FieldsForm
):
id
=
'forgottenpasswordrequest'
__regid__
=
'forgottenpasswordrequest'
form_buttons
=
[
wdg
.
SubmitButton
()]
@
property
def
action
(
self
):
return
self
.
req
.
build_url
(
u
'forgottenpassword-requestconfirm'
)
return
self
.
_cw
.
build_url
(
u
'forgottenpassword-requestconfirm'
)
upassword
=
ff
.
StringField
(
widget
=
wdg
.
PasswordInput
(),
required
=
True
)
class
ForgottenPasswordRequestView
(
form
.
FormViewMixIn
,
StartupView
):
id
=
'forgottenpasswordrequest'
__regid__
=
'forgottenpasswordrequest'
def
check_key
(
self
):
try
:
return
decrypt
(
self
.
req
.
form
[
'key'
],
self
.
vreg
.
config
[
'cypher-seed'
])
return
decrypt
(
self
.
_cw
.
form
[
'key'
],
self
.
vreg
.
config
[
'cypher-seed'
])
except
:
msg
=
self
.
req
.
_
(
u
'Invalid link. Please try again.'
)
raise
Redirect
(
self
.
req
.
build_url
(
u
'forgottenpassword'
,
__message
=
msg
))
msg
=
self
.
_cw
.
_
(
u
'Invalid link. Please try again.'
)
raise
Redirect
(
self
.
_cw
.
build_url
(
u
'forgottenpassword'
,
__message
=
msg
))
def
call
(
self
):
key
=
self
.
check_key
()
form
=
self
.
vreg
[
'forms'
].
select
(
'forgottenpasswordrequest'
,
self
.
req
)
form
=
self
.
vreg
[
'forms'
].
select
(
'forgottenpasswordrequest'
,
self
.
_cw
)
form
.
form_add_hidden
(
'use_email'
,
key
[
'use_email'
])
form
.
form_add_hidden
(
'revocation_id'
,
key
[
'revocation_id'
])
self
.
w
(
u
'<p>%s</p>'
%
self
.
req
.
_
(
u
'Update your password:'
))
self
.
w
(
u
'<p>%s</p>'
%
self
.
_cw
.
_
(
u
'Update your password:'
))
self
.
w
(
form
.
render
())
class
ForgottenPasswordRequestConfirm
(
controller
.
Controller
):
id
=
'forgottenpassword-requestconfirm'
__regid__
=
'forgottenpassword-requestconfirm'
def
publish
(
self
,
rset
=
None
):
data
=
self
.
checked_data
()
...
...
@@ -144,18 +144,18 @@ class ForgottenPasswordRequestConfirm(controller.Controller):
raise
Redirect
(
self
.
build_url
(
'pwdreset'
,
__message
=
msg
))
def
checked_data
(
self
):
fieldsform
=
self
.
vreg
[
'forms'
].
select
(
'forgottenpasswordrequest'
,
self
.
req
)
fieldsform
=
self
.
vreg
[
'forms'
].
select
(
'forgottenpasswordrequest'
,
self
.
_cw
)
data
=
{}
errors
=
{}
for
field
in
fieldsform
.
_fields_
:
value
=
self
.
req
.
form
.
get
(
field
.
name
,
u
''
).
strip
()
value
=
self
.
_cw
.
form
.
get
(
field
.
name
,
u
''
).
strip
()
if
not
value
:
if
field
.
required
:
errors
[
field
.
name
]
=
self
.
req
.
_
(
'required attribute'
)
errors
[
field
.
name
]
=
self
.
_cw
.
_
(
'required attribute'
)
data
[
field
.
name
]
=
value
data
[
'use_email'
]
=
self
.
req
.
form
.
get
(
'use_email'
,
''
).
strip
()
data
[
'revocation_id'
]
=
self
.
req
.
form
.
get
(
'revocation_id'
,
''
).
strip
()
if
data
[
'upassword'
]
!=
self
.
req
.
form
.
get
(
'upassword-confirm'
):
data
[
'use_email'
]
=
self
.
_cw
.
form
.
get
(
'use_email'
,
''
).
strip
()
data
[
'revocation_id'
]
=
self
.
_cw
.
form
.
get
(
'revocation_id'
,
''
).
strip
()
if
data
[
'upassword'
]
!=
self
.
_cw
.
form
.
get
(
'upassword-confirm'
):
errors
[
'upassword'
]
=
_
(
'passwords are different'
)
if
errors
:
raise
ValidationError
(
None
,
errors
)
...
...
@@ -163,7 +163,7 @@ class ForgottenPasswordRequestConfirm(controller.Controller):
class
PasswordResetView
(
StartupView
):
id
=
'pwdreset'
__regid__
=
'pwdreset'
def
call
(
self
):
self
.
wview
(
'index'
,
self
.
rset
)
...
...
@@ -174,13 +174,14 @@ class PasswordResetView(StartupView):
from
cubicweb.server.repository
import
Repository
from
logilab.common.decorators
import
monkeypatch
from
cubicweb.
common.
mail
import
format_mail
from
cubicweb.mail
import
format_mail
@
monkeypatch
(
Repository
)
def
forgotpwd_send_email
(
self
,
data
):
session
=
self
.
internal_session
()
revocation_limit
=
self
.
config
[
'revocation-limit'
]
revocation_id
=
u
''
.
join
([
random
.
choice
(
string
.
letters
+
string
.
digits
)
for
x
in
range
(
10
)])
revocation_id
=
u
''
.
join
([
random
.
choice
(
string
.
letters
+
string
.
digits
)
for
x
in
xrange
(
10
)])
revocation_date
=
datetime
.
now
()
+
timedelta
(
minutes
=
revocation_limit
)
try
:
existing_requests
=
session
.
execute
(
'Any F WHERE U primary_email E, E address %(e)s, U has_fpasswd F'
,
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment