diff --git a/apps/assets/api.py b/apps/assets/api.py index 96cb06f42..4771f222e 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ from rest_framework import viewsets, generics, mixins + from rest_framework.response import Response from rest_framework.views import APIView from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView @@ -90,34 +91,19 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): permission_classes = (IsSuperUser,) -class SystemUserAuthApi(APIView): +class SystemUserAuthInfoApi(generics.RetrieveAPIView): + queryset = SystemUser.objects.all() permission_classes = (IsSuperUserOrAppUser,) - def get(self, request, *args, **kwargs): - system_user_id = request.query_params.get('system_user_id', -1) - system_user_username = request.query_params.get('system_user_username', '') - - system_user = get_object_or_none(SystemUser, id=system_user_id, username=system_user_username) - - if system_user: - if system_user.password: - password = signer.sign(system_user.password) - else: - password = signer.sign('') - - if system_user.private_key: - private_key = signer.sign(system_user.private_key) - else: - private_key = signer.sign(None) - - response = { - 'id': system_user.id, - 'password': password, - 'private_key': private_key, - } - - return Response(response) - else: - return Response({'msg': 'error system user id or username'}, status=401) - + def retrieve(self, request, *args, **kwargs): + system_user = self.get_object() + data = { + 'id': system_user.id, + 'name': system_user.name, + 'username': system_user.username, + 'password': system_user.password, + 'private_key': system_user.private_key, + 'auth_method': system_user.auth_method, + } + return Response(data) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index aeb7062cb..acbdd3aec 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -263,7 +263,7 @@ class SystemUserForm(forms.ModelForm): class Meta: model = SystemUser fields = [ - 'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'as_default', + 'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'auth_method', 'auto_push', 'auto_update', 'sudo', 'comment', 'shell', 'home', 'uid', ] widgets = { @@ -273,8 +273,8 @@ class SystemUserForm(forms.ModelForm): help_texts = { 'name': '* required', 'username': '* required', - 'auth_push': 'Auto push system user to asset', - 'auth_update': 'Auto update system user ssh key', + 'auto_push': 'Auto push system user to asset', + 'auto_update': 'Auto update system user ssh key', } diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 0e9af99de..584472146 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -95,13 +95,18 @@ class SystemUser(models.Model): PROTOCOL_CHOICES = ( ('ssh', 'ssh'), ) + AUTH_METHOD_CHOICES = ( + ('P', 'Password'), + ('K', 'Public key'), + ) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) username = models.CharField(max_length=16, verbose_name=_('Username')) _password = models.CharField(max_length=256, blank=True, verbose_name=_('Password')) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) _private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key')) _public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key')) - as_default = models.BooleanField(default=False, verbose_name=_('As default')) + auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K', + max_length=1, verbose_name=_('Auth method')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) auto_update = models.BooleanField(default=True, verbose_name=_('Auto update pass/key')) sudo = models.TextField(max_length=4096, default='/user/bin/whoami', verbose_name=_('Sudo')) diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 6e6353a86..bddd91fc4 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -17,6 +17,7 @@ class AssetGroupSerializer(serializers.ModelSerializer): def get_assets_amount(obj): return obj.assets.count() + class AssetUpdateGroupSerializer(serializers.ModelSerializer): groups = serializers.PrimaryKeyRelatedField(many=True, queryset=AssetGroup.objects.all()) @@ -24,6 +25,7 @@ class AssetUpdateGroupSerializer(serializers.ModelSerializer): model = Asset fields = ['id', 'groups'] + class AssetUpdateSystemUserSerializer(serializers.ModelSerializer): system_users = serializers.PrimaryKeyRelatedField(many=True, queryset=SystemUser.objects.all()) @@ -31,6 +33,7 @@ class AssetUpdateSystemUserSerializer(serializers.ModelSerializer): model = Asset fields = ['id', 'system_users'] + class AdminUserSerializer(serializers.ModelSerializer): class Meta: model = AdminUser @@ -52,6 +55,12 @@ class SystemUserSerializer(serializers.ModelSerializer): return fields +class SystemUserSimpleSerializer(serializers.ModelSerializer): + class Meta: + model = SystemUser + fields = ('id', 'name', 'username') + + class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): # system_users = SystemUserSerializer(many=True, read_only=True) # admin_user = AdminUserSerializer(many=False, read_only=True) @@ -75,7 +84,7 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): class AssetGrantedSerializer(serializers.ModelSerializer): - system_users = SystemUserSerializer(many=True, read_only=True) + system_users = SystemUserSimpleSerializer(many=True, read_only=True) is_inherited = serializers.SerializerMethodField() system_users_join = serializers.SerializerMethodField() diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 6e0a2b2c7..18234ba8a 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -16,11 +16,12 @@ router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user') urlpatterns = [ url(r'^v1/assets_bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), # url(r'^v1/idc/(?P[0-9]+)/assets/$', api.IDCAssetsApi.as_view(), name='api-idc-assets'), - url(r'^v1/system-user/auth/', api.SystemUserAuthApi.as_view(), name='system-user-auth'), + url(r'^v1/system-user/(?P[0-9]+)/auth-info/', api.SystemUserAuthInfoApi.as_view(), + name='system-user-auth-info'), url(r'^v1/assets/(?P\d+)/groups/$', api.AssetUpdateGroupApi.as_view(), name='asset-update-group'), url(r'^v1/assets/(?P\d+)/system-users/$', - api.SystemUserUpdateApi.as_view(), name='asset-update-systemusers'), + api.SystemUserUpdateApi.as_view(), name='asset-update-system-users'), ] urlpatterns += router.urls diff --git a/apps/audits/models.py b/apps/audits/models.py index b2da116e3..6c276d658 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -18,7 +18,6 @@ class LoginLog(models.Model): username = models.CharField(max_length=20, verbose_name=_('Username')) name = models.CharField(max_length=20, blank=True, verbose_name=_('Name')) login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type')) - terminal = models.CharField(max_length=32, verbose_name=_('Terminal')) login_ip = models.GenericIPAddressField(verbose_name=_('Login ip')) login_city = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Login city')) user_agent = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('User agent')) diff --git a/apps/audits/utils.py b/apps/audits/utils.py index 303ba18c9..00190e755 100644 --- a/apps/audits/utils.py +++ b/apps/audits/utils.py @@ -17,25 +17,28 @@ def validate_ip(ip): return False -def write_login_log(username, name='', login_type='W', - terminal='', login_ip='', user_agent=''): +def write_login_log(username, name='', login_type='', + login_ip='', user_agent=''): if not (login_ip and validate_ip(login_ip)): login_ip = '0.0.0.0' if not name: name = username login_city = get_ip_city(login_ip) - LoginLog.objects.create(username=username, name=name, login_type=login_type, login_ip=login_ip, - terminal=terminal, login_city=login_city, user_agent=user_agent) + LoginLog.objects.create(username=username, name=name, login_type=login_type, + login_ip=login_ip, login_city=login_city, user_agent=user_agent) def get_ip_city(ip, timeout=10): # Taobao ip api: http://ip.taobao.com//service/getIpInfo.php?ip=8.8.8.8 - # Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=js + # Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=json - url = 'http://ip.taobao.com/service/getIpInfo.php?ip=' + ip - r = requests.get(url, timeout=timeout) + url = 'http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=%s&format=json' % ip + try: + r = requests.get(url, timeout=timeout) + except requests.Timeout: + r = None city = 'Unknown' - if r.status_code == 200: + if r and r.status_code == 200: try: data = r.json() if data['code'] == 0: diff --git a/apps/users/api.py b/apps/users/api.py index 8772adbdf..765893e44 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -100,7 +100,7 @@ class UserToken(APIView): user, msg = check_user_valid(username=username, email=email, password=password, public_key=public_key) if user: - token = generate_token(request) + token = generate_token(request, user) return Response({'Token': token, 'key': 'Bearer'}, status=200) else: return Response({'error': msg}, status=406) @@ -114,28 +114,22 @@ class UserProfile(APIView): class UserAuthApi(APIView): - permission_classes = () - expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 + permission_classes = (AllowAny,) def post(self, request, *args, **kwargs): username = request.data.get('username', '') password = request.data.get('password', '') public_key = request.data.get('public_key', '') - remote_addr = request.data.get('remote_addr', '') - terminal = request.data.get('applications', '') - login_type = request.data.get('login_type', 'T') - user = check_user_valid(username=username, password=password, public_key=public_key) + login_type = request.data.get('login_type', '') + login_ip = request.META.get('REMOTE_ADDR', '') + user_agent = request.data.get('HTTP_USER_AGENT', '') + + user, msg = check_user_valid(username=username, password=password, public_key=public_key) if user: - token = cache.get('%s_%s' % (user.id, remote_addr)) - if not token: - token = generate_token(request) - - cache.set(token, user.id, self.expiration) - cache.set('%s_%s' % (user.id, remote_addr), token, self.expiration) - write_login_log_async.delay(user.username, name=user.name, terminal=terminal, - login_ip=remote_addr, login_type=login_type) - return Response({'token': token, 'id': user.id, 'username': user.username, - 'name': user.name, 'is_active': user.is_active}) + token = generate_token(request, user) + write_login_log_async.delay(user.username, name=user.name, user_agent=user_agent, + login_ip=login_ip, login_type=login_type) + return Response({'token': token, 'user': user.to_json()}) else: - return Response({'msg': 'Invalid password or public key or user is not active or expired'}, status=401) + return Response({'msg': msg}, status=401) diff --git a/apps/users/authentication.py b/apps/users/authentication.py index 6647bfcfd..5bd6843f0 100644 --- a/apps/users/authentication.py +++ b/apps/users/authentication.py @@ -43,7 +43,6 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): msg = _('Invalid signature header. Signature string should not contain spaces.') raise exceptions.AuthenticationFailed(msg) - try: sign = auth[1].decode().split(':') if len(sign) != 2: @@ -58,7 +57,8 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): return self.authenticate_credentials(request, access_key_id, request_signature) - def authenticate_credentials(self, request, access_key_id, request_signature): + @staticmethod + def authenticate_credentials(request, access_key_id, request_signature): access_key = get_object_or_none(AccessKey, id=access_key_id) request_date = get_request_date_header(request) if access_key is None or not access_key.user: @@ -109,7 +109,8 @@ class AccessTokenAuthentication(authentication.BaseAuthentication): raise exceptions.AuthenticationFailed(msg) return self.authenticate_credentials(token) - def authenticate_credentials(self, token): + @staticmethod + def authenticate_credentials(token): user_id = cache.get(token) user = get_object_or_none(User, id=user_id) diff --git a/apps/users/urls/api_urls.py b/apps/users/urls/api_urls.py index 34401d525..212837aff 100644 --- a/apps/users/urls/api_urls.py +++ b/apps/users/urls/api_urls.py @@ -17,6 +17,7 @@ router.register(r'v1/user-groups', api.UserGroupViewSet, 'user-group') urlpatterns = [ url(r'^v1/token/$', api.UserToken.as_view(), name='user-token'), url(r'^v1/profile/$', api.UserProfile.as_view(), name='user-profile'), + url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'), url(r'^v1/users/(?P\d+)/password/reset/$', api.UserResetPasswordApi.as_view(), name='user-reset-password'), url(r'^v1/users/(?P\d+)/public-key/reset/$', api.UserResetPKApi.as_view(), name='user-public-key-reset'), url(r'^v1/users/(?P\d+)/public-key/update/$', api.UserUpdatePKApi.as_view(), name='user-public-key-update'), diff --git a/apps/users/utils.py b/apps/users/utils.py index 418cc60a6..41931cb3a 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -180,8 +180,8 @@ def send_reset_ssh_key_mail(user): def check_user_valid(**kwargs): password = kwargs.pop('password', None) public_key = kwargs.pop('public_key', None) - email = kwargs.pop('email') - username = kwargs.pop('username') + email = kwargs.pop('email', None) + username = kwargs.pop('username', None) if username: user = get_object_or_none(User, username=username) @@ -206,24 +206,23 @@ def check_user_valid(**kwargs): elif len(public_key_saved) > 1: if public_key == public_key_saved[1]: return user, '' - return None, _('Passowrd or SSH public key invalid') + return None, _('Password or SSH public key invalid') -def refresh_token(token, user): - expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 +def refresh_token(token, user, expiration=3600): cache.set(token, user.id, expiration) -def generate_token(request): +def generate_token(request, user): expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 remote_addr = request.META.get('REMOTE_ADDR', '') remote_addr = base64.b16encode(remote_addr).replace('=', '') - token = cache.get('%s_%s' % (request.user.id, remote_addr)) + token = cache.get('%s_%s' % (user.id, remote_addr)) if not token: token = uuid.uuid4().get_hex() print('Set cache: %s' % token) - cache.set(token, request.user.id, expiration) - cache.set('%s_%s' % (request.user.id, remote_addr), token, expiration) + cache.set(token, user.id, expiration) + cache.set('%s_%s' % (user.id, remote_addr), token, expiration) return token