mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 05:39:59 +00:00
Merge branch '6.3'
This commit is contained in:
@@ -34,18 +34,16 @@ logger = logging.getLogger(__name__)
|
|||||||
def get_org_user_info(org_id, email):
|
def get_org_user_info(org_id, email):
|
||||||
user_info = {}
|
user_info = {}
|
||||||
|
|
||||||
user_obj = User.objects.get(email=email)
|
|
||||||
user_info['org_id'] = org_id
|
user_info['org_id'] = org_id
|
||||||
user_info['active'] = user_obj.is_active
|
|
||||||
user_info['email'] = email
|
user_info['email'] = email
|
||||||
user_info['name'] = email2nickname(email)
|
user_info['name'] = email2nickname(email)
|
||||||
user_info['contact_email'] = email2contact_email(email)
|
user_info['contact_email'] = email2contact_email(email)
|
||||||
|
|
||||||
org_user_quota = seafile_api.get_org_user_quota(org_id, email)
|
org_user_quota = seafile_api.get_org_user_quota(org_id, email)
|
||||||
user_info['quota_total'] = org_user_quota / get_file_size_unit('MB')
|
user_info['quota_total'] = org_user_quota
|
||||||
|
|
||||||
org_user_quota_usage = seafile_api.get_org_user_quota_usage(org_id, email)
|
org_user_quota_usage = seafile_api.get_org_user_quota_usage(org_id, email)
|
||||||
user_info['quota_usage'] = org_user_quota_usage / get_file_size_unit('MB')
|
user_info['quota_usage'] = org_user_quota_usage
|
||||||
|
|
||||||
return user_info
|
return user_info
|
||||||
|
|
||||||
@@ -94,6 +92,32 @@ class AdminOrgUsers(APIView):
|
|||||||
throttle_classes = (UserRateThrottle,)
|
throttle_classes = (UserRateThrottle,)
|
||||||
permission_classes = (IsAdminUser, IsProVersion)
|
permission_classes = (IsAdminUser, IsProVersion)
|
||||||
|
|
||||||
|
def get(self, request, org_id):
|
||||||
|
""" Get all users in an org.
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. only admin can perform this action.
|
||||||
|
"""
|
||||||
|
# argument check
|
||||||
|
org_id = int(org_id)
|
||||||
|
if org_id == 0:
|
||||||
|
error_msg = 'org_id invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
org = ccnet_api.get_org_by_id(org_id)
|
||||||
|
if not org:
|
||||||
|
error_msg = 'Organization %d not found.' % org_id
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
org_users = ccnet_api.get_org_emailusers(org.url_prefix, -1, -1)
|
||||||
|
for org_user in org_users:
|
||||||
|
user_info = get_org_user_info(org_id, org_user.email)
|
||||||
|
user_info['active'] = org_user.is_active
|
||||||
|
result.append(user_info)
|
||||||
|
|
||||||
|
return Response({'users': result})
|
||||||
|
|
||||||
def post(self, request, org_id):
|
def post(self, request, org_id):
|
||||||
""" Add new user to org.
|
""" Add new user to org.
|
||||||
|
|
||||||
@@ -121,6 +145,14 @@ class AdminOrgUsers(APIView):
|
|||||||
error_msg = 'password invalid.'
|
error_msg = 'password invalid.'
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
active = request.POST.get('active', 'true')
|
||||||
|
active = active.lower()
|
||||||
|
if active not in ('true', 'false'):
|
||||||
|
error_msg = 'active invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
is_active = active == 'true'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
User.objects.get(email=email)
|
User.objects.get(email=email)
|
||||||
user_exists = True
|
user_exists = True
|
||||||
@@ -147,7 +179,8 @@ class AdminOrgUsers(APIView):
|
|||||||
|
|
||||||
# create user
|
# create user
|
||||||
try:
|
try:
|
||||||
User.objects.create_user(email, password, is_staff=False, is_active=True)
|
User.objects.create_user(email, password, is_staff=False,
|
||||||
|
is_active=is_active)
|
||||||
except User.DoesNotExist as e:
|
except User.DoesNotExist as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Fail to add user %s.' % email
|
error_msg = 'Fail to add user %s.' % email
|
||||||
@@ -170,6 +203,7 @@ class AdminOrgUsers(APIView):
|
|||||||
UserOptions.objects.set_force_passwd_change(email)
|
UserOptions.objects.set_force_passwd_change(email)
|
||||||
|
|
||||||
user_info = get_org_user_info(org_id, email)
|
user_info = get_org_user_info(org_id, email)
|
||||||
|
user_info['active'] = is_active
|
||||||
return Response(user_info)
|
return Response(user_info)
|
||||||
|
|
||||||
|
|
||||||
@@ -188,7 +222,24 @@ class AdminOrgUser(APIView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# argument check
|
# argument check
|
||||||
|
org_id = int(org_id)
|
||||||
|
if org_id == 0:
|
||||||
|
error_msg = 'org_id invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
org = ccnet_api.get_org_by_id(org_id)
|
||||||
|
if not org:
|
||||||
|
error_msg = 'Organization %d not found.' % org_id
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_obj = User.objects.get(email=email)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
error_msg = 'User %s not found.' % email
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
user_info = get_org_user_info(org_id, email)
|
user_info = get_org_user_info(org_id, email)
|
||||||
|
user_info['active'] = user_obj.is_active
|
||||||
return Response(user_info)
|
return Response(user_info)
|
||||||
|
|
||||||
@check_org_user
|
@check_org_user
|
||||||
@@ -199,6 +250,12 @@ class AdminOrgUser(APIView):
|
|||||||
1. only admin can perform this action.
|
1. only admin can perform this action.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = User.objects.get(email=email)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
error_msg = 'User %s not found.' % email
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
# update active
|
# update active
|
||||||
active = request.data.get('active', None)
|
active = request.data.get('active', None)
|
||||||
if active:
|
if active:
|
||||||
@@ -207,7 +264,6 @@ class AdminOrgUser(APIView):
|
|||||||
error_msg = "active invalid, should be 'true' or 'false'."
|
error_msg = "active invalid, should be 'true' or 'false'."
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
user = User.objects.get(email=email)
|
|
||||||
if active == 'true':
|
if active == 'true':
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
else:
|
else:
|
||||||
@@ -267,6 +323,7 @@ class AdminOrgUser(APIView):
|
|||||||
seafile_api.set_org_user_quota(org_id, email, user_quota)
|
seafile_api.set_org_user_quota(org_id, email, user_quota)
|
||||||
|
|
||||||
user_info = get_org_user_info(org_id, email)
|
user_info = get_org_user_info(org_id, email)
|
||||||
|
user_info['active'] = user.is_active
|
||||||
return Response(user_info)
|
return Response(user_info)
|
||||||
|
|
||||||
@check_org_user
|
@check_org_user
|
||||||
|
@@ -9,8 +9,6 @@ from rest_framework import status
|
|||||||
|
|
||||||
from seaserv import ccnet_api, seafile_api
|
from seaserv import ccnet_api, seafile_api
|
||||||
|
|
||||||
from seaserv import seafserv_threaded_rpc
|
|
||||||
|
|
||||||
from seahub.utils.file_size import get_file_size_unit
|
from seahub.utils.file_size import get_file_size_unit
|
||||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
||||||
@@ -40,12 +38,11 @@ except ImportError:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def get_org_info(org_id):
|
def get_org_info(org):
|
||||||
|
|
||||||
|
org_id = org.org_id
|
||||||
|
|
||||||
org_info = {}
|
org_info = {}
|
||||||
|
|
||||||
org = ccnet_api.get_org_by_id(org_id)
|
|
||||||
|
|
||||||
org_info['org_id'] = org_id
|
org_info['org_id'] = org_id
|
||||||
org_info['org_name'] = org.org_name
|
org_info['org_name'] = org.org_name
|
||||||
org_info['ctime'] = timestamp_to_isoformat_timestr(org.ctime)
|
org_info['ctime'] = timestamp_to_isoformat_timestr(org.ctime)
|
||||||
@@ -57,12 +54,44 @@ def get_org_info(org_id):
|
|||||||
org_info['creator_contact_email'] = email2contact_email(creator)
|
org_info['creator_contact_email'] = email2contact_email(creator)
|
||||||
|
|
||||||
org_info['quota'] = seafile_api.get_org_quota(org_id)
|
org_info['quota'] = seafile_api.get_org_quota(org_id)
|
||||||
|
org_info['quota_usage'] = seafile_api.get_org_quota_usage(org_id)
|
||||||
|
|
||||||
if ORG_MEMBER_QUOTA_ENABLED:
|
if ORG_MEMBER_QUOTA_ENABLED:
|
||||||
org_info['max_user_number'] = OrgMemberQuota.objects.get_quota(org_id)
|
org_info['max_user_number'] = OrgMemberQuota.objects.get_quota(org_id)
|
||||||
|
|
||||||
return org_info
|
return org_info
|
||||||
|
|
||||||
|
class AdminOrganizations(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAdminUser, IsProVersion)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
""" Get all organizations
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. only admin can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not (CLOUD_MODE and MULTI_TENANCY):
|
||||||
|
error_msg = 'Feature is not enabled.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
orgs = ccnet_api.get_all_orgs(-1, -1)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for org in orgs:
|
||||||
|
org_info = get_org_info(org)
|
||||||
|
result.append(org_info)
|
||||||
|
|
||||||
|
return Response({'organizations': result})
|
||||||
|
|
||||||
|
|
||||||
class AdminOrganization(APIView):
|
class AdminOrganization(APIView):
|
||||||
|
|
||||||
@@ -92,7 +121,7 @@ class AdminOrganization(APIView):
|
|||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
org_info = get_org_info(org_id)
|
org_info = get_org_info(org)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
@@ -174,7 +203,7 @@ class AdminOrganization(APIView):
|
|||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
org_info = get_org_info(org_id)
|
org_info = get_org_info(org)
|
||||||
return Response(org_info)
|
return Response(org_info)
|
||||||
|
|
||||||
def delete(self, request, org_id):
|
def delete(self, request, org_id):
|
||||||
|
@@ -8,6 +8,7 @@ from seahub.auth import authenticate
|
|||||||
from seahub.api2.models import DESKTOP_PLATFORMS
|
from seahub.api2.models import DESKTOP_PLATFORMS
|
||||||
from seahub.api2.utils import get_token_v1, get_token_v2
|
from seahub.api2.utils import get_token_v1, get_token_v2
|
||||||
from seahub.profile.models import Profile
|
from seahub.profile.models import Profile
|
||||||
|
from seahub.two_factor.models import default_device
|
||||||
from seahub.two_factor.views.login import is_device_remembered
|
from seahub.two_factor.views.login import is_device_remembered
|
||||||
from seahub.utils.two_factor_auth import has_two_factor_auth, \
|
from seahub.utils.two_factor_auth import has_two_factor_auth, \
|
||||||
two_factor_auth_enabled, verify_two_factor_token
|
two_factor_auth_enabled, verify_two_factor_token
|
||||||
@@ -118,10 +119,13 @@ class AuthTokenSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
token = request.META.get('HTTP_X_SEAFILE_OTP', '')
|
token = request.META.get('HTTP_X_SEAFILE_OTP', '')
|
||||||
if not token:
|
if not token:
|
||||||
|
# Generate challenge(send sms/call/...) if token is not provided.
|
||||||
|
default_device(user).generate_challenge()
|
||||||
|
|
||||||
self.two_factor_auth_failed = True
|
self.two_factor_auth_failed = True
|
||||||
msg = 'Two factor auth token is missing.'
|
msg = 'Two factor auth token is missing.'
|
||||||
raise serializers.ValidationError(msg)
|
raise serializers.ValidationError(msg)
|
||||||
if not verify_two_factor_token(user.username, token):
|
if not verify_two_factor_token(user, token):
|
||||||
self.two_factor_auth_failed = True
|
self.two_factor_auth_failed = True
|
||||||
msg = 'Two factor auth token is invalid.'
|
msg = 'Two factor auth token is invalid.'
|
||||||
raise serializers.ValidationError(msg)
|
raise serializers.ValidationError(msg)
|
||||||
|
@@ -8,5 +8,9 @@ class CheckPasswordHash(object):
|
|||||||
"""Logout user if value of hash key in session is not equal to current password hash"""
|
"""Logout user if value of hash key in session is not equal to current password hash"""
|
||||||
def process_view(self, request, *args, **kwargs):
|
def process_view(self, request, *args, **kwargs):
|
||||||
if getattr(request.user, 'is_authenticated') and request.user.is_authenticated():
|
if getattr(request.user, 'is_authenticated') and request.user.is_authenticated():
|
||||||
|
if request.user.enc_password == '!':
|
||||||
|
# Disable for LDAP/Shibboleth/SAML/... users.
|
||||||
|
return None
|
||||||
|
|
||||||
if request.session.get(PASSWORD_HASH_KEY) != get_password_hash(request.user):
|
if request.session.get(PASSWORD_HASH_KEY) != get_password_hash(request.user):
|
||||||
logout(request)
|
logout(request)
|
||||||
|
@@ -5,6 +5,8 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from seahub.profile.models import Profile, DetailedProfile
|
from seahub.profile.models import Profile, DetailedProfile
|
||||||
|
|
||||||
|
from seahub.settings import ENABLE_UPDATE_USER_INFO
|
||||||
|
|
||||||
class ProfileForm(forms.Form):
|
class ProfileForm(forms.Form):
|
||||||
nickname = forms.CharField(max_length=64, required=False)
|
nickname = forms.CharField(max_length=64, required=False)
|
||||||
intro = forms.CharField(max_length=256, required=False)
|
intro = forms.CharField(max_length=256, required=False)
|
||||||
@@ -13,6 +15,9 @@ class ProfileForm(forms.Form):
|
|||||||
"""
|
"""
|
||||||
Validates that nickname should not include '/'
|
Validates that nickname should not include '/'
|
||||||
"""
|
"""
|
||||||
|
if not ENABLE_UPDATE_USER_INFO:
|
||||||
|
raise forms.ValidationError(_(u"Permission denied."))
|
||||||
|
|
||||||
if "/" in self.cleaned_data["nickname"]:
|
if "/" in self.cleaned_data["nickname"]:
|
||||||
raise forms.ValidationError(_(u"Name should not include '/'."))
|
raise forms.ValidationError(_(u"Name should not include '/'."))
|
||||||
|
|
||||||
|
@@ -29,7 +29,9 @@
|
|||||||
<li class="tab"><a href="#two-factor-auth">{% trans "Two-Factor Authentication" %}</a></li>
|
<li class="tab"><a href="#two-factor-auth">{% trans "Two-Factor Authentication" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ENABLE_DELETE_ACCOUNT %}
|
||||||
<li class="tab" id="del-account-nav"><a href="#del-account">{% trans "Delete Account" %}</a></li>
|
<li class="tab" id="del-account-nav"><a href="#del-account">{% trans "Delete Account" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -48,10 +50,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="" method="post">{% csrf_token %}
|
<form action="" method="post">{% csrf_token %}
|
||||||
<label>{% trans "Name:" context "true name" %}</label><input type="text" name="nickname" value="{{ form.data.nickname }}" class="input" />
|
<label>{% trans "Name:" context "true name" %}</label><input type="text" name="nickname" value="{{ form.data.nickname }}" class="input" {% if not ENABLE_UPDATE_USER_INFO %} disabled {% endif %}/>
|
||||||
|
|
||||||
|
{% if ENABLE_UPDATE_USER_INFO %}
|
||||||
{% for error in form.nickname.errors %}
|
{% for error in form.nickname.errors %}
|
||||||
<span class="error">{{ error|escape }}</span>
|
<span class="error">{{ error|escape }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
{% if form.data.login_id %}
|
{% if form.data.login_id %}
|
||||||
@@ -67,14 +72,15 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if form.telephone %}
|
{% if form.telephone %}
|
||||||
<label>{% trans "Telephone:" %}</label><input type="text" name="telephone" value="{{ form.data.telephone }}" class="input" />
|
<label>{% trans "Telephone:" %}</label><input type="text" name="telephone" value="{{ form.data.telephone }}" class="input" {% if not ENABLE_UPDATE_USER_INFO %} disabled {% endif %} />
|
||||||
{% for error in form.telephone.errors %}
|
{% for error in form.telephone.errors %}
|
||||||
<span class="error">{{ error|escape }}</span>
|
<span class="error">{{ error|escape }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if ENABLE_UPDATE_USER_INFO %}
|
||||||
<input type="submit" value="{% trans "Submit" %}" class="submit vh" />
|
<input type="submit" value="{% trans "Submit" %}" class="submit vh" />
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -165,6 +171,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ENABLE_DELETE_ACCOUNT %}
|
||||||
<div class="setting-item" id="del-account">
|
<div class="setting-item" id="del-account">
|
||||||
<h3>{% trans "Delete Account" %}</h3>
|
<h3>{% trans "Delete Account" %}</h3>
|
||||||
<p class="txt-before-btn">{% trans "This operation will not be reverted. Please think twice!" %}</p>
|
<p class="txt-before-btn">{% trans "This operation will not be reverted. Please think twice!" %}</p>
|
||||||
@@ -173,6 +180,7 @@
|
|||||||
<input type="submit" value="{% trans "Delete" %}" class="submit" />
|
<input type="submit" value="{% trans "Delete" %}" class="submit" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div> <!-- right-panel -->
|
</div> <!-- right-panel -->
|
||||||
</div> <!-- row -->
|
</div> <!-- row -->
|
||||||
@@ -228,6 +236,7 @@ $('#user-avatar-input').on('change', function() {
|
|||||||
|
|
||||||
$('#user-basic-info .submit').css({'margin-left': $('#user-basic-info label').outerWidth(true)}).removeClass('vh');
|
$('#user-basic-info .submit').css({'margin-left': $('#user-basic-info label').outerWidth(true)}).removeClass('vh');
|
||||||
|
|
||||||
|
{% if ENABLE_DELETE_ACCOUNT %}
|
||||||
$('#account-delete-btn').on('click', function () {
|
$('#account-delete-btn').on('click', function () {
|
||||||
var title = "{% trans "Delete Account" %}",
|
var title = "{% trans "Delete Account" %}",
|
||||||
con = "{% trans "Really want to delete your account?" %}";
|
con = "{% trans "Really want to delete your account?" %}";
|
||||||
@@ -240,6 +249,7 @@ $('#account-delete-btn').on('click', function () {
|
|||||||
$('#account-delete-form').trigger('submit');
|
$('#account-delete-form').trigger('submit');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if ENABLE_ADDRESSBOOK_OPT_IN %}
|
{% if ENABLE_ADDRESSBOOK_OPT_IN %}
|
||||||
$("#list-in-address-book input[type='checkbox']").on('change', function() {
|
$("#list-in-address-book input[type='checkbox']").on('change', function() {
|
||||||
|
@@ -23,6 +23,8 @@ from seahub.utils import is_ldap_user
|
|||||||
from seahub.utils.two_factor_auth import has_two_factor_auth
|
from seahub.utils.two_factor_auth import has_two_factor_auth
|
||||||
from seahub.views import get_owned_repo_list
|
from seahub.views import get_owned_repo_list
|
||||||
|
|
||||||
|
from seahub.settings import ENABLE_DELETE_ACCOUNT, ENABLE_UPDATE_USER_INFO
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_profile(request):
|
def edit_profile(request):
|
||||||
"""
|
"""
|
||||||
@@ -96,6 +98,8 @@ def edit_profile(request):
|
|||||||
'two_factor_auth_enabled': has_two_factor_auth(),
|
'two_factor_auth_enabled': has_two_factor_auth(),
|
||||||
'ENABLE_CHANGE_PASSWORD': settings.ENABLE_CHANGE_PASSWORD,
|
'ENABLE_CHANGE_PASSWORD': settings.ENABLE_CHANGE_PASSWORD,
|
||||||
'ENABLE_WEBDAV_SECRET': settings.ENABLE_WEBDAV_SECRET,
|
'ENABLE_WEBDAV_SECRET': settings.ENABLE_WEBDAV_SECRET,
|
||||||
|
'ENABLE_DELETE_ACCOUNT': ENABLE_DELETE_ACCOUNT,
|
||||||
|
'ENABLE_UPDATE_USER_INFO': ENABLE_UPDATE_USER_INFO,
|
||||||
'webdav_passwd': webdav_passwd,
|
'webdav_passwd': webdav_passwd,
|
||||||
'email_notification_interval': email_inverval,
|
'email_notification_interval': email_inverval,
|
||||||
}
|
}
|
||||||
@@ -177,6 +181,11 @@ def get_user_profile(request, user):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def delete_user_account(request):
|
def delete_user_account(request):
|
||||||
|
if not ENABLE_DELETE_ACCOUNT:
|
||||||
|
messages.error(request, _(u'Permission denied.'))
|
||||||
|
next = request.META.get('HTTP_REFERER', settings.SITE_ROOT)
|
||||||
|
return HttpResponseRedirect(next)
|
||||||
|
|
||||||
if request.method != 'POST':
|
if request.method != 'POST':
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
@@ -365,6 +365,9 @@ FORCE_PASSWORD_CHANGE = True
|
|||||||
# Enable a user to change password in 'settings' page.
|
# Enable a user to change password in 'settings' page.
|
||||||
ENABLE_CHANGE_PASSWORD = True
|
ENABLE_CHANGE_PASSWORD = True
|
||||||
|
|
||||||
|
ENABLE_DELETE_ACCOUNT = True
|
||||||
|
ENABLE_UPDATE_USER_INFO = True
|
||||||
|
|
||||||
# Enable or disable repo history setting
|
# Enable or disable repo history setting
|
||||||
ENABLE_REPO_HISTORY_SETTING = True
|
ENABLE_REPO_HISTORY_SETTING = True
|
||||||
|
|
||||||
|
@@ -3,6 +3,9 @@
|
|||||||
watermark.load({
|
watermark.load({
|
||||||
{% if request.user.username %}
|
{% if request.user.username %}
|
||||||
watermark_txt: "{{site_name}} {{request.user.username|email2nickname|escapejs}}",
|
watermark_txt: "{{site_name}} {{request.user.username|email2nickname|escapejs}}",
|
||||||
|
{# used when view shared file #}
|
||||||
|
{% elif shared_by %}
|
||||||
|
watermark_txt: "{{site_name}} {{shared_by|email2nickname|escapejs}}",
|
||||||
{% else %}
|
{% else %}
|
||||||
watermark_txt: "{% trans "Anonymous User" %}",
|
watermark_txt: "{% trans "Anonymous User" %}",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
26
seahub/two_factor/gateways/seaf_messenger.py
Normal file
26
seahub/two_factor/gateways/seaf_messenger.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SeafMessenger(object):
|
||||||
|
@staticmethod
|
||||||
|
def make_call(device, token):
|
||||||
|
logger.info('Fake call to %s: "Your token is: %s"', device.number, token)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_sms(device, token):
|
||||||
|
api_token = settings.SEAF_MESSAGER_API_TOKEN
|
||||||
|
url = settings.SEAF_MESSAGER_SMS_API
|
||||||
|
|
||||||
|
values = {
|
||||||
|
'phone_num': device.number,
|
||||||
|
'code': token,
|
||||||
|
}
|
||||||
|
requests.post(url, data=values,
|
||||||
|
headers={'Authorization': 'Token %s' % api_token})
|
@@ -215,14 +215,13 @@ def handle_two_factor_auth(request, user, redirect_to):
|
|||||||
request.session[SESSION_KEY_TWO_FACTOR_FAILED_ATTEMPT] = 0
|
request.session[SESSION_KEY_TWO_FACTOR_FAILED_ATTEMPT] = 0
|
||||||
return redirect(reverse('two_factor_auth'))
|
return redirect(reverse('two_factor_auth'))
|
||||||
|
|
||||||
def verify_two_factor_token(username, token):
|
def verify_two_factor_token(user, token):
|
||||||
"""
|
"""
|
||||||
This function is called when doing the api authentication. We only support
|
This function is called when doing the api authentication.
|
||||||
totp here to simply the case. Backup token is not supported, because if the
|
Backup token is not supported, because if the user has the backup token,
|
||||||
user has the backup token, he can always login the website and re-setup the
|
he can always login the website and re-setup the totp.
|
||||||
totp.
|
|
||||||
"""
|
"""
|
||||||
device = TOTPDevice.objects.device_for_user(username)
|
device = default_device(user)
|
||||||
if device:
|
if device:
|
||||||
return device.verify_token(token)
|
return device.verify_token(token)
|
||||||
|
|
||||||
|
@@ -113,7 +113,7 @@ from seahub.api2.endpoints.admin.upload_links import AdminUploadLink, \
|
|||||||
AdminUploadLinkUpload, AdminUploadLinkCheckPassword
|
AdminUploadLinkUpload, AdminUploadLinkCheckPassword
|
||||||
from seahub.api2.endpoints.admin.users_batch import AdminUsersBatch
|
from seahub.api2.endpoints.admin.users_batch import AdminUsersBatch
|
||||||
from seahub.api2.endpoints.admin.operation_logs import AdminOperationLogs
|
from seahub.api2.endpoints.admin.operation_logs import AdminOperationLogs
|
||||||
from seahub.api2.endpoints.admin.organizations import AdminOrganization
|
from seahub.api2.endpoints.admin.organizations import AdminOrganizations, AdminOrganization
|
||||||
from seahub.api2.endpoints.admin.org_users import AdminOrgUsers, AdminOrgUser
|
from seahub.api2.endpoints.admin.org_users import AdminOrgUsers, AdminOrgUser
|
||||||
from seahub.api2.endpoints.admin.org_stats import AdminOrgStatsTraffic
|
from seahub.api2.endpoints.admin.org_stats import AdminOrgStatsTraffic
|
||||||
from seahub.api2.endpoints.admin.logo import AdminLogo
|
from seahub.api2.endpoints.admin.logo import AdminLogo
|
||||||
@@ -457,6 +457,7 @@ urlpatterns = [
|
|||||||
url(r'^api/v2.1/admin/admin-role/$', AdminAdminRole.as_view(), name='api-v2.1-admin-admin-role'),
|
url(r'^api/v2.1/admin/admin-role/$', AdminAdminRole.as_view(), name='api-v2.1-admin-admin-role'),
|
||||||
|
|
||||||
## admin::organizations
|
## admin::organizations
|
||||||
|
url(r'^api/v2.1/admin/organizations/$', AdminOrganizations.as_view(), name='api-v2.1-admin-organizations'),
|
||||||
url(r'^api/v2.1/admin/organizations/(?P<org_id>\d+)/$', AdminOrganization.as_view(), name='api-v2.1-admin-organization'),
|
url(r'^api/v2.1/admin/organizations/(?P<org_id>\d+)/$', AdminOrganization.as_view(), name='api-v2.1-admin-organization'),
|
||||||
url(r'^api/v2.1/admin/organizations/(?P<org_id>\d+)/users/$', AdminOrgUsers.as_view(), name='api-v2.1-admin-org-users'),
|
url(r'^api/v2.1/admin/organizations/(?P<org_id>\d+)/users/$', AdminOrgUsers.as_view(), name='api-v2.1-admin-org-users'),
|
||||||
url(r'^api/v2.1/admin/organizations/(?P<org_id>\d+)/users/(?P<email>[^/]+)/$', AdminOrgUser.as_view(), name='api-v2.1-admin-org-user'),
|
url(r'^api/v2.1/admin/organizations/(?P<org_id>\d+)/users/(?P<email>[^/]+)/$', AdminOrgUser.as_view(), name='api-v2.1-admin-org-user'),
|
||||||
|
@@ -133,7 +133,7 @@ define([
|
|||||||
if (this.currentView == this.dirView) {
|
if (this.currentView == this.dirView) {
|
||||||
if ($('#upload-file-dialog').is(':visible') &&
|
if ($('#upload-file-dialog').is(':visible') &&
|
||||||
$('#upload-file-dialog .status').text() == window.fileuploading) {
|
$('#upload-file-dialog .status').text() == window.fileuploading) {
|
||||||
if (!window.confirm('A file is being uploaded. Are you sure you want to leave this page?')) {
|
if (!window.confirm(gettext('A file is being uploaded. Are you sure you want to leave this page?'))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -57,6 +57,30 @@ class OrgUsersTest(BaseTestCase):
|
|||||||
remove_org(self.org_id)
|
remove_org(self.org_id)
|
||||||
self.remove_user(self.org_creator)
|
self.remove_user(self.org_creator)
|
||||||
|
|
||||||
|
def test_can_get_users(self):
|
||||||
|
|
||||||
|
if not LOCAL_PRO_DEV_ENV:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.login_as(self.admin)
|
||||||
|
resp = self.client.get(self.org_users_url)
|
||||||
|
json_resp = json.loads(resp.content)
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
|
||||||
|
users = json_resp['organizaton_members']
|
||||||
|
assert len(users) > 0
|
||||||
|
assert users[0]['org_id'] == self.org_id
|
||||||
|
assert users[0]['email'] == self.org_creator
|
||||||
|
|
||||||
|
def test_can_not_get_users_if_not_admin(self):
|
||||||
|
|
||||||
|
if not LOCAL_PRO_DEV_ENV:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.login_as(self.user)
|
||||||
|
resp = self.client.get(self.org_users_url)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
def test_can_create(self):
|
def test_can_create(self):
|
||||||
|
|
||||||
if not LOCAL_PRO_DEV_ENV:
|
if not LOCAL_PRO_DEV_ENV:
|
||||||
|
85
tests/api/endpoints/admin/test_orgs.py
Normal file
85
tests/api/endpoints/admin/test_orgs.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from seaserv import ccnet_api
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from seahub.test_utils import BaseTestCase
|
||||||
|
from tests.common.utils import randstring
|
||||||
|
|
||||||
|
from seaserv import seafserv_threaded_rpc
|
||||||
|
|
||||||
|
try:
|
||||||
|
from seahub.settings import LOCAL_PRO_DEV_ENV
|
||||||
|
except ImportError:
|
||||||
|
LOCAL_PRO_DEV_ENV = False
|
||||||
|
|
||||||
|
def remove_org(org_id):
|
||||||
|
org_id = int(org_id)
|
||||||
|
org = ccnet_api.get_org_by_id(org_id)
|
||||||
|
if org:
|
||||||
|
users =ccnet_api.get_org_emailusers(org.url_prefix, -1, -1)
|
||||||
|
for u in users:
|
||||||
|
ccnet_api.remove_org_user(org_id, u.email)
|
||||||
|
|
||||||
|
groups = ccnet_api.get_org_groups(org.org_id, -1, -1)
|
||||||
|
for g in groups:
|
||||||
|
ccnet_api.remove_org_group(org_id, g.gid)
|
||||||
|
|
||||||
|
# remove org repos
|
||||||
|
seafserv_threaded_rpc.remove_org_repo_by_org_id(org_id)
|
||||||
|
|
||||||
|
# remove org
|
||||||
|
ccnet_api.remove_org(org_id)
|
||||||
|
|
||||||
|
class OrgsTest(BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
self.user_name = self.user.username
|
||||||
|
self.admin_name = self.admin.username
|
||||||
|
|
||||||
|
if LOCAL_PRO_DEV_ENV:
|
||||||
|
self.org_name = randstring(6)
|
||||||
|
self.org_url_prefix = randstring(6)
|
||||||
|
tmp_user = self.create_user(email='%s@%s.com' % (randstring(6), randstring(6)))
|
||||||
|
self.org_creator = tmp_user.username
|
||||||
|
self.org_id = ccnet_api.create_org(self.org_name,
|
||||||
|
self.org_url_prefix, self.org_creator)
|
||||||
|
self.orgs_url = reverse('api-v2.1-admin-organizations')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.remove_group()
|
||||||
|
self.remove_repo()
|
||||||
|
|
||||||
|
if LOCAL_PRO_DEV_ENV:
|
||||||
|
remove_org(self.org_id)
|
||||||
|
self.remove_user(self.org_creator)
|
||||||
|
|
||||||
|
def test_can_get_orgs(self):
|
||||||
|
|
||||||
|
if not LOCAL_PRO_DEV_ENV:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.login_as(self.admin)
|
||||||
|
resp = self.client.get(self.orgs_url)
|
||||||
|
json_resp = json.loads(resp.content)
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
|
||||||
|
users = json_resp['organizations']
|
||||||
|
assert len(users) > 0
|
||||||
|
assert users[0].has_key('org_id')
|
||||||
|
assert users[0].has_key('org_name')
|
||||||
|
assert users[0].has_key('ctime')
|
||||||
|
assert users[0].has_key('org_url_prefix')
|
||||||
|
assert users[0].has_key('creator_email')
|
||||||
|
assert users[0].has_key('creator_name')
|
||||||
|
assert users[0].has_key('creator_contact_email')
|
||||||
|
assert users[0].has_key('quota')
|
||||||
|
|
||||||
|
def test_can_not_get_orgs_if_not_admin(self):
|
||||||
|
|
||||||
|
if not LOCAL_PRO_DEV_ENV:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.login_as(self.user)
|
||||||
|
resp = self.client.get(self.orgs_url)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
Reference in New Issue
Block a user