将配置文件模型连接到远程用户(自定义身份验证后端)

时间:2016-12-28 16:17:36

标签: python django authentication

我正在编写自定义身份验证后端,以针对RESTful API进行身份验证。

我不知道如何以一种不受用户名更改等影响的方式将Profile模型(包含不在远程数据库中的信息)连接到这些用户。例如,如果我想为这些用户提供“生物”字段,那么我通常会这样做:

from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField(blank=True)

    def __str__(self):
        return self.user.email

这是否可以使用基于API的自定义身份验证后端?如果是这样,我会把它连接到远程用户的OneToOneField中添加什么?

我是否必须确保身份验证后端更新用户的本地数据库,然后Profile模型将连接到该用户?这就是我要尝试这个的方法,但我想我会向社区询问如何在其他地方完成这项工作。

1 个答案:

答案 0 :(得分:1)

希望这可以帮助将来的某个人。我解决这个问题的方法是在我的身份验证后端我查询API然后找到(或创建)本地用户对象并更新该对象的相关属性。它有点奇怪,因为本地数据库的主键是远程用户对象的id(和pk)(以便快速查找get_user()),但我使用用户名作为查找键进行身份验证。 / p>

所以,总结一下:

  • 制作后端查询API,然后将本地数据库与该用户的远程数据库同步(本地数据库只存在于用户对象的远程数据中)。
  • 为确保您的系统即使在远程用户名更改时也能正常运行,请确保使用远程ID(可能无法更改)来同步本地数据库。
  • 确保本地用户模型的主键是远程数据库的主键,以帮助自己完成数据完整性。

此外,在我的示例中,我必须下载整个帐户列表并为正确的用户grep它。在我的实际示例中,我使用的API允许我按用户名进行查找。下载整个帐户列表是一个疯狂的坏主意,我只是在这里做,因为我的测试API不支持。

models.py

class RemoteUserManager(BaseUserManager):
    def create_user(self, remote_id, remote_username=None, remote_first_name=None, remote_last_name=None, remote_email=None):
        if not remote_id:
            raise ValueError('Users must have a remote_id')
        user = self.model(
            remote_id=remote_id,
            remote_username=remote_username,
            remote_first_name=remote_first_name,
            remote_last_name=remote_last_name,
            remote_email=remote_email,
        )
        user.save(using=self._db)
        return user
    def create_superuser(self, remote_id, remote_username=None, remote_first_name=None, remote_last_name=None, remote_email=None):
        user = self.create_user(
            remote_id=remote_id,
            remote_username=remote_username,
            remote_first_name=remote_first_name,
            remote_last_name=remote_last_name,
            remote_email=remote_email,
        )
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user

class RemoteUser(AbstractBaseUser):
    remote_id = models.IntegerField(primary_key=True)
    remote_username = models.CharField(max_length=255, blank=True)
    remote_first_name = models.CharField(max_length=255, blank=True)
    remote_last_name = models.CharField(max_length=255, blank=True)
    remote_email = models.EmailField(blank=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=True)     # testing login to admin interface
    is_superuser = models.BooleanField(default=True) # testing login to admin interface
    created = models.DateTimeField(auto_now_add=True)
    last_login = models.DateTimeField(auto_now=True)
    objects = RemoteUserManager()
    USERNAME_FIELD = 'remote_username'
    REQUIRED_FIELDS = []
    def has_perm(self, perm, obj=None):
        return True
    def has_module_perms(self, app_label):
        return True
    def get_full_name(self):
        return str(self.remote_first_name + ' ' + self.remote_last_name).strip()
    def get_short_name(self):
        return self.remote_first_name
    def get_display_name(self):
        if (self.remote_first_name):
            return self.remote_first_name
        else:
            return self.remote_email
    def __str__(self):
        return self.remote_username

backends.py

class RemoteAuthBackend(object):
    apikey = 'yourlongandsecureapikeygoeshere'
    target = 'https://remote.domain.com'
    list_call = '/admin/scaffolds/accounts/list.json'
    show_call = '/admin/scaffolds/accounts/show/'

    def authenticate(self, username=None, password=None):
        username = username.strip()
        # run API call and find user by username
        request = urllib.request.Request(self.target + self.list_call + '?api_key=' + self.apikey)
        response = urllib.request.urlopen(request)
        resp_parsed = json.loads(response.read().decode('utf-8'))
        # go through list of dicts and find the matching username
        match_user = None
        for user_record in resp_parsed:
            if user_record.get('login', None) == username:
                match_user = user_record
                print('testy')
                break
        if not match_user: return None
        # get password-crypted and salt
        crypted_password = match_user.get('crypted_password', None)
        salt = match_user.get('salt', None)
        # hash password and see if the digests match (base64 encoded sha1 digest)
        hash = b64encode(sha1((salt + password).encode('utf-8')).digest()).decode('utf-8')
        if hash == match_user.get('crypted_password'):
            # update user object that matches remote_id and return local user object
            try: local_user = models.RemoteUser.objects.get(pk=match_user['id'])
            except: local_user = None
            if not local_user:
                try: local_user = models.RemoteUser(remote_id=match_user['id'])
                except: return None   # This should never happen, ever
            local_user.remote_username = match_user.get('login', None)
            local_user.remote_first_name = match_user.get('first_name', None)
            local_user.remote_last_name = match_user.get('last_name', None)
            local_user.remote_email = match_user.get('email', None)
            try: local_user.save()
            except: return None
            return local_user
        else:
            # failed auth
            return None

    def get_user(self, user_id):
        # get user from remote and sync up local object properties based on remote_id
        request = urllib.request.Request(self.target + self.show_call + str(user_id) + '.json?api_key=' + self.apikey)
        response = urllib.request.urlopen(request)
        match_user = json.loads(response.read().decode('utf-8'))
        if not match_user: return None
        try: local_user = models.RemoteUser.objects.get(pk=match_user['id'])
        except: local_user = None
        if not local_user:
            try: local_user = models.RemoteUser(remote_id=match_user['id'])
            except: return None   # This should never happen, ever
        local_user.remote_username = match_user.get('login', None)
        local_user.remote_first_name = match_user.get('first_name', None)
        local_user.remote_last_name = match_user.get('last_name', None)
        local_user.remote_email = match_user.get('email', None)
        try: local_user.save()
        except: return None
        return local_user

您仍然需要像使用自定义用户模型一样修改admin.py