44
44
from flask .ext .login import (LoginManager , login_required , login_user ,
45
45
logout_user , current_user , make_secure_token )
46
46
from flask .ext .bcrypt import Bcrypt
47
- from coil .utils import USER_FIELDS , PERMISSIONS , SiteProxy
47
+ from coil .utils import USER_FIELDS , PERMISSIONS , PERMISSIONS_E , SiteProxy
48
48
from coil .forms import (LoginForm , NewPostForm , NewPageForm , DeleteForm ,
49
49
UserDeleteForm , UserEditForm , AccountForm ,
50
- PermissionsForm )
50
+ PermissionsForm , UserImportForm )
51
51
52
52
_site = None
53
53
site = None
@@ -366,7 +366,7 @@ class User(object):
366
366
def __init__ (self , uid , username , realname , password , email , active ,
367
367
is_admin , can_edit_all_posts , wants_all_posts ,
368
368
can_upload_attachments , can_rebuild_site ,
369
- can_transfer_post_authorship ):
369
+ can_transfer_post_authorship , must_change_password ):
370
370
"""Initialize an user with specified settings."""
371
371
self .uid = int (uid )
372
372
self .username = username
@@ -380,6 +380,7 @@ def __init__(self, uid, username, realname, password, email, active,
380
380
self .can_upload_attachments = can_upload_attachments
381
381
self .can_rebuild_site = can_rebuild_site
382
382
self .can_transfer_post_authorship = can_transfer_post_authorship
383
+ self .must_change_password = must_change_password
383
384
384
385
def get_id (self ):
385
386
"""Get user ID."""
@@ -419,7 +420,7 @@ def get_user(uid):
419
420
d = db .hgetall ('user:{0}' .format (uid ))
420
421
if d :
421
422
for p in PERMISSIONS :
422
- d [p ] = d [ p ] == '1'
423
+ d [p ] = d . get ( p ) == '1'
423
424
return User (uid = uid , ** d )
424
425
else :
425
426
return None
@@ -511,6 +512,9 @@ def index():
511
512
512
513
:param int all: Whether or not should show all posts
513
514
"""
515
+ if current_user .must_change_password :
516
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
517
+
514
518
context = {'postform' : NewPostForm (),
515
519
'pageform' : NewPageForm (),
516
520
'delform' : DeleteForm ()}
@@ -559,6 +563,9 @@ def edit(path):
559
563
560
564
:param path: Path to post to edit.
561
565
"""
566
+ if current_user .must_change_password :
567
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
568
+
562
569
context = {'path' : path , 'site' : site }
563
570
post = find_post (path )
564
571
if post is None :
@@ -628,6 +635,9 @@ def edit(path):
628
635
@login_required
629
636
def delete ():
630
637
"""Delete a post."""
638
+ if current_user .must_change_password :
639
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
640
+
631
641
form = DeleteForm ()
632
642
path = request .form ['path' ]
633
643
post = find_post (path )
@@ -687,6 +697,9 @@ def api_rebuild():
687
697
@login_required
688
698
def rebuild (mode = '' ):
689
699
"""Rebuild the site with a nice UI."""
700
+ if current_user .must_change_password :
701
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
702
+
690
703
scan_site () # for good measure
691
704
if not current_user .can_rebuild_site :
692
705
return error ('You are not permitted to rebuild the site.</p>'
@@ -705,55 +718,16 @@ def rebuild(mode=''):
705
718
return render ('coil_rebuild.tmpl' , {'title' : 'Rebuild' })
706
719
707
720
708
- @app .route ('/bower_components/<path:path>' )
709
- def serve_bower_components (path ):
710
- """Serve bower components.
711
-
712
- This is meant to be used ONLY by the internal dev server.
713
- Please configure your web server to handle requests to this URL::
714
-
715
- /bower_components/ => coil/data/bower_components
716
- """
717
- res = pkg_resources .resource_filename (
718
- 'coil' , os .path .join ('data' , 'bower_components' ))
719
- return send_from_directory (res , path )
720
-
721
-
722
- @app .route ('/coil_assets/<path:path>' )
723
- def serve_coil_assets (path ):
724
- """Serve Coil assets.
725
-
726
- This is meant to be used ONLY by the internal dev server.
727
- Please configure your web server to handle requests to this URL::
728
-
729
- /coil_assets/ => coil/data/coil_assets
730
- """
731
- res = pkg_resources .resource_filename (
732
- 'coil' , os .path .join ('data' , 'coil_assets' ))
733
- return send_from_directory (res , path )
734
-
735
-
736
- @app .route ('/assets/<path:path>' )
737
- def serve_assets (path ):
738
- """Serve Nikola assets.
739
-
740
- This is meant to be used ONLY by the internal dev server.
741
- Please configure your web server to handle requests to this URL::
742
-
743
- /assets/ => output/assets
744
- """
745
- res = os .path .join (app .config ['NIKOLA_ROOT' ],
746
- _site .config ["OUTPUT_FOLDER" ], 'assets' )
747
- return send_from_directory (res , path )
748
-
749
-
750
721
@app .route ('/new/<obj>/' , methods = ['POST' ])
751
722
@login_required
752
723
def new (obj ):
753
724
"""Create a new post or page.
754
725
755
726
:param str obj: Object to create (post or page)
756
727
"""
728
+ if current_user .must_change_password :
729
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
730
+
757
731
title = request .form ['title' ]
758
732
_site .config ['ADDITIONAL_METADATA' ]['author.uid' ] = current_user .uid
759
733
try :
@@ -789,15 +763,61 @@ def new(obj):
789
763
return redirect (url_for ('index' ))
790
764
791
765
766
+ @app .route ('/bower_components/<path:path>' )
767
+ def serve_bower_components (path ):
768
+ """Serve bower components.
769
+
770
+ This is meant to be used ONLY by the internal dev server.
771
+ Please configure your web server to handle requests to this URL::
772
+
773
+ /bower_components/ => coil/data/bower_components
774
+ """
775
+ res = pkg_resources .resource_filename (
776
+ 'coil' , os .path .join ('data' , 'bower_components' ))
777
+ return send_from_directory (res , path )
778
+
779
+
780
+ @app .route ('/coil_assets/<path:path>' )
781
+ def serve_coil_assets (path ):
782
+ """Serve Coil assets.
783
+
784
+ This is meant to be used ONLY by the internal dev server.
785
+ Please configure your web server to handle requests to this URL::
786
+
787
+ /coil_assets/ => coil/data/coil_assets
788
+ """
789
+ res = pkg_resources .resource_filename (
790
+ 'coil' , os .path .join ('data' , 'coil_assets' ))
791
+ return send_from_directory (res , path )
792
+
793
+
794
+ @app .route ('/assets/<path:path>' )
795
+ def serve_assets (path ):
796
+ """Serve Nikola assets.
797
+
798
+ This is meant to be used ONLY by the internal dev server.
799
+ Please configure your web server to handle requests to this URL::
800
+
801
+ /assets/ => output/assets
802
+ """
803
+ res = os .path .join (app .config ['NIKOLA_ROOT' ],
804
+ _site .config ["OUTPUT_FOLDER" ], 'assets' )
805
+ return send_from_directory (res , path )
806
+
807
+
792
808
@app .route ('/account/' , methods = ['POST' , 'GET' ])
793
809
@login_required
794
810
def acp_account ():
795
811
"""Manage the user account of currently-logged-in users.
796
812
797
813
This does NOT accept admin-specific options.
798
814
"""
799
- alert = ''
800
- alert_status = ''
815
+ if request .args .get ('status' ) == 'pwdchange' :
816
+ alert = 'You must change your password before proceeding.'
817
+ alert_status = 'danger'
818
+ else :
819
+ alert = ''
820
+ alert_status = ''
801
821
action = 'edit'
802
822
form = AccountForm ()
803
823
if request .method == 'POST' :
@@ -809,6 +829,7 @@ def acp_account():
809
829
if data ['newpwd1' ] == data ['newpwd2' ] and check_password (
810
830
current_user .password , data ['oldpwd' ]):
811
831
current_user .password = password_hash (data ['newpwd1' ])
832
+ current_user .must_change_password = False
812
833
else :
813
834
alert = 'Passwords don’t match.'
814
835
alert_status = 'danger'
@@ -830,6 +851,11 @@ def acp_account():
830
851
@login_required
831
852
def acp_users ():
832
853
"""List all users."""
854
+ if current_user .must_change_password :
855
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
856
+ elif not current_user .is_admin :
857
+ return error ("Not authorized to edit users." , 401 )
858
+
833
859
alert = ''
834
860
alert_status = ''
835
861
if request .args .get ('status' ) == 'deleted' :
@@ -838,8 +864,6 @@ def acp_users():
838
864
if request .args .get ('status' ) == 'undeleted' :
839
865
alert = 'User undeleted.'
840
866
alert_status = 'success'
841
- if not current_user .is_admin :
842
- return error ("Not authorized to edit users." , 401 )
843
867
else :
844
868
uids = db .hgetall ('users' ).values ()
845
869
USERS = sorted ([(i , get_user (i )) for i in uids ])
@@ -849,16 +873,18 @@ def acp_users():
849
873
'alert' : alert ,
850
874
'alert_status' : alert_status ,
851
875
'delform' : UserDeleteForm (),
852
- 'editform' : UserEditForm ()})
876
+ 'editform' : UserEditForm (),
877
+ 'importform' : UserImportForm ()})
853
878
854
879
855
880
@app .route ('/users/edit/' , methods = ['POST' ])
856
881
@login_required
857
882
def acp_users_edit ():
858
883
"""Edit an user account."""
859
884
global current_user
860
-
861
- if not current_user .is_admin :
885
+ if current_user .must_change_password :
886
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
887
+ elif not current_user .is_admin :
862
888
return error ("Not authorized to edit users." , 401 )
863
889
data = request .form
864
890
@@ -873,6 +899,7 @@ def acp_users_edit():
873
899
uid = max (db .hgetall ('users' ).values ()) + 1
874
900
pf = [False for p in PERMISSIONS ]
875
901
pf [0 ] = True # active
902
+ pf [7 ] = True # must_change_password
876
903
user = User (uid , data ['username' ], '' , '' , '' , * pf )
877
904
write_user (user )
878
905
db .hset ('users' , user .username , user .uid )
@@ -911,6 +938,7 @@ def acp_users_edit():
911
938
user .active = True
912
939
if user .uid == current_user .uid :
913
940
user .is_admin = True
941
+ user .must_change_password = False
914
942
current_user = user
915
943
write_user (user )
916
944
@@ -924,12 +952,33 @@ def acp_users_edit():
924
952
'form' : form })
925
953
926
954
955
+ @app .route ('/users/import/' , methods = ['POST' ])
956
+ @login_required
957
+ def acp_users_import ():
958
+ """Import users from a TSV file."""
959
+ if current_user .must_change_password :
960
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
961
+ elif not current_user .is_admin :
962
+ return error ("Not authorized to edit users." , 401 )
963
+
964
+ form = UserImportForm ()
965
+ if not form .validate ():
966
+ return error ("Bad Request" , 400 )
967
+ fh = request .files ['tsv' ].stream
968
+ tsv = fh .read ()
969
+ return tsv
970
+ # TODO
971
+
972
+
927
973
@app .route ('/users/delete/' , methods = ['POST' ])
928
974
@login_required
929
975
def acp_users_delete ():
930
976
"""Delete or undelete an user account."""
931
- if not current_user .is_admin :
977
+ if current_user .must_change_password :
978
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
979
+ elif not current_user .is_admin :
932
980
return error ("Not authorized to edit users." , 401 )
981
+
933
982
form = UserDeleteForm ()
934
983
if not form .validate ():
935
984
return error ("Bad Request" , 400 )
@@ -950,7 +999,9 @@ def acp_users_delete():
950
999
@login_required
951
1000
def acp_users_permissions ():
952
1001
"""Change user permissions."""
953
- if not current_user .is_admin :
1002
+ if current_user .must_change_password :
1003
+ return redirect (url_for ('acp_account' ) + '?status=pwdchange' )
1004
+ elif not current_user .is_admin :
954
1005
return error ("Not authorized to edit users." , 401 )
955
1006
956
1007
form = PermissionsForm ()
@@ -966,9 +1017,11 @@ def acp_users_permissions():
966
1017
setattr (user , perm , True )
967
1018
else :
968
1019
setattr (user , perm , False )
969
- if uid == current_user .uid :
970
- user .is_admin = True # cannot deadmin oneself
971
- user .active = True # cannot deactivate oneself
1020
+ if int (uid ) == current_user .uid :
1021
+ # Some permissions cannot apply to the current user.
1022
+ user .is_admin = True
1023
+ user .active = True
1024
+ user .must_change_password = False
972
1025
write_user (user )
973
1026
users .append ((uid , user ))
974
1027
action = 'save'
@@ -981,8 +1034,8 @@ def display_permission(user, permission):
981
1034
if permission == 'wants_all_posts' and not user .can_edit_all_posts :
982
1035
# If this happens, permissions are damaged.
983
1036
checked = ''
984
- if user .uid == current_user .uid and permission in ['active' ,
985
- 'is_admin' ] :
1037
+ if ( user .uid == current_user .uid and permission in [
1038
+ 'active' , 'is_admin' , 'must_change_password' ]) :
986
1039
disabled = 'disabled'
987
1040
else :
988
1041
disabled = ''
@@ -993,13 +1046,14 @@ def display_permission(user, permission):
993
1046
'data-perm="{4}" class="u{0}" {2} {3}>' )
994
1047
return d .format (user .uid , permission , checked , disabled , permission_a )
995
1048
996
- users = [(i , get_user (i )) for i in uids ]
1049
+ if not users :
1050
+ users = [(i , get_user (i )) for i in uids ]
997
1051
998
1052
return render ('coil_users_permissions.tmpl' ,
999
1053
context = {'title' : 'Permissions' ,
1000
1054
'USERS' : sorted (users ),
1001
1055
'UIDS' : sorted (uids ),
1002
- 'PERMISSIONS ' : PERMISSIONS ,
1056
+ 'PERMISSIONS_E ' : PERMISSIONS_E ,
1003
1057
'action' : action ,
1004
1058
'json' : json ,
1005
1059
'form' : form ,
0 commit comments