bugfix in oauth toolkit
This commit is contained in:
parent
03f83767c3
commit
6256c340c9
10 changed files with 288 additions and 22 deletions
|
|
@ -195,6 +195,7 @@ STATIC_URL = '/static/'
|
||||||
# CLIENT_SECRET = config('CLIENT_SECRET')
|
# CLIENT_SECRET = config('CLIENT_SECRET')
|
||||||
|
|
||||||
CELERY_BROKER_URL = config('CELERY_BROKER_URL')
|
CELERY_BROKER_URL = config('CELERY_BROKER_URL')
|
||||||
|
|
||||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
USE_X_FORWARDED_HOST = True
|
USE_X_FORWARDED_HOST = True
|
||||||
USE_X_FORWARDED_PORT = True
|
USE_X_FORWARDED_PORT = True
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import base64
|
import base64
|
||||||
|
import binascii
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from urllib.parse import unquote_plus
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
# import service_clients
|
# import service_clients
|
||||||
|
|
@ -9,6 +11,7 @@ from django.utils.timezone import make_aware
|
||||||
from oauth2_provider.models import get_access_token_model
|
from oauth2_provider.models import get_access_token_model
|
||||||
from oauth2_provider.oauth2_validators import OAuth2Validator as BaseOAuth2Validator
|
from oauth2_provider.oauth2_validators import OAuth2Validator as BaseOAuth2Validator
|
||||||
from .settings import oauth2_settings
|
from .settings import oauth2_settings
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
log = logging.getLogger("oauth2_provider")
|
log = logging.getLogger("oauth2_provider")
|
||||||
|
|
||||||
|
|
@ -208,3 +211,51 @@ class OAuth2Validator(BaseOAuth2Validator): # pylint: disable=w0223
|
||||||
# else:
|
# else:
|
||||||
# self._set_oauth2_error_on_request(request, access_token, scopes)
|
# self._set_oauth2_error_on_request(request, access_token, scopes)
|
||||||
# return False
|
# return False
|
||||||
|
|
||||||
|
def _authenticate_basic_auth(self, request):
|
||||||
|
"""
|
||||||
|
Authenticates with HTTP Basic Auth.
|
||||||
|
|
||||||
|
Note: as stated in rfc:`2.3.1`, client_id and client_secret must be encoded with
|
||||||
|
"application/x-www-form-urlencoded" encoding algorithm.
|
||||||
|
"""
|
||||||
|
auth_string = self._extract_basic_auth(request)
|
||||||
|
if not auth_string:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
encoding = request.encoding or settings.DEFAULT_CHARSET or "utf-8"
|
||||||
|
except AttributeError:
|
||||||
|
encoding = "utf-8"
|
||||||
|
|
||||||
|
try:
|
||||||
|
b64_decoded = base64.b64decode(auth_string)
|
||||||
|
except (TypeError, binascii.Error):
|
||||||
|
log.debug("Failed basic auth: %r can't be decoded as base64", auth_string)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
auth_string_decoded = b64_decoded.decode(encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
log.debug("Failed basic auth: %r can't be decoded as unicode by %r", auth_string, encoding)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
client_id, client_secret = map(unquote_plus, auth_string_decoded.split(":", 1))
|
||||||
|
except ValueError:
|
||||||
|
log.debug("Failed basic auth, Invalid base64 encoding.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._load_application(client_id, request) is None:
|
||||||
|
log.debug("Failed basic auth: Application %s does not exist" % client_id)
|
||||||
|
return False
|
||||||
|
elif request.client.client_id != client_id:
|
||||||
|
log.debug("Failed basic auth: wrong client id %s" % client_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# TODO: check why not work
|
||||||
|
elif not client_secret == request.client.client_secret:
|
||||||
|
log.debug("Failed basic auth: wrong client secret %s" % client_secret)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,3 @@ from model_utils.choices import Choices
|
||||||
|
|
||||||
MAX_OTP_TRY = 3
|
MAX_OTP_TRY = 3
|
||||||
DEVELOPMENT_PHONE_NUMBERS = ['+989999999999', '+989999999998']
|
DEVELOPMENT_PHONE_NUMBERS = ['+989999999999', '+989999999998']
|
||||||
|
|
||||||
STATE_CHOICES = Choices(
|
|
||||||
(1, 'created', 'created'),
|
|
||||||
(2, 'delayed', 'delayed'), # delayed as user wish
|
|
||||||
(3, 'pending', 'pending'), # wait for external service response
|
|
||||||
(4, 'incomplete', 'incomplete'), # started and wait for internal progress to complete
|
|
||||||
(5, 'success', 'success'),
|
|
||||||
(6, 'failed', 'failed'),
|
|
||||||
(7, 'expected_failure', 'expected_failure'), # no exact data available but guessed to be failed
|
|
||||||
)
|
|
||||||
|
|
@ -190,7 +190,7 @@ class User(AbstractUser):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.pk} - {self.username}"
|
return self.name or self.get_full_name() or self.username or self.phone_number or self.email or _('no name')
|
||||||
|
|
||||||
|
|
||||||
def create_user_in_introspection(token, content):
|
def create_user_in_introspection(token, content):
|
||||||
|
|
|
||||||
11
apps/wallet/constans.py
Normal file
11
apps/wallet/constans.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from model_utils.choices import Choices
|
||||||
|
|
||||||
|
STATE_CHOICES = Choices(
|
||||||
|
(1, 'created', 'created'),
|
||||||
|
(2, 'delayed', 'delayed'), # delayed as user wish
|
||||||
|
(3, 'pending', 'pending'), # wait for external service response
|
||||||
|
(4, 'incomplete', 'incomplete'), # started and wait for internal progress to complete
|
||||||
|
(5, 'success', 'success'),
|
||||||
|
(6, 'failed', 'failed'),
|
||||||
|
(7, 'expected_failure', 'expected_failure'), # no exact data available but guessed to be failed
|
||||||
|
)
|
||||||
|
|
@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from model_utils.choices import Choices
|
from model_utils.choices import Choices
|
||||||
from rest_framework.exceptions import APIException, ValidationError
|
from rest_framework.exceptions import APIException, ValidationError
|
||||||
|
|
||||||
from apps.users.constans import STATE_CHOICES
|
from .constans import STATE_CHOICES
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from rest_framework.response import Response
|
||||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||||||
from apps.wallet.models import Transaction
|
from apps.wallet.models import Transaction
|
||||||
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer
|
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer
|
||||||
from apps.users.constans import STATE_CHOICES
|
from .constans import STATE_CHOICES
|
||||||
|
|
||||||
|
|
||||||
def get_application_client_id(request):
|
def get_application_client_id(request):
|
||||||
|
|
@ -24,7 +24,25 @@ def get_application_client_id(request):
|
||||||
application_client_id = None
|
application_client_id = None
|
||||||
return application_client_id
|
return application_client_id
|
||||||
|
|
||||||
|
# TODO: this is user invoice create view
|
||||||
|
# class TransactionList(generics.ListCreateAPIView):
|
||||||
|
# permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
|
# serializer_class = TransactionSerializer
|
||||||
|
# required_alternate_scopes = {
|
||||||
|
# "GET": [['wallet.transaction:list']],
|
||||||
|
# "POST": [['wallet.invoice:create']],
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# def get_queryset(self):
|
||||||
|
# user = self.request.user
|
||||||
|
# return Transaction.objects.filter(Q(payee=user) | Q(payer=user)).all()
|
||||||
|
#
|
||||||
|
# def perform_create(self, serializer):
|
||||||
|
# user = self.request.user
|
||||||
|
# serializer.save(payee=user, application_client_id=get_application_client_id(self.request))
|
||||||
|
|
||||||
|
|
||||||
|
# this is application create invoice view
|
||||||
class TransactionList(generics.ListCreateAPIView):
|
class TransactionList(generics.ListCreateAPIView):
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
serializer_class = TransactionSerializer
|
serializer_class = TransactionSerializer
|
||||||
|
|
@ -38,8 +56,9 @@ class TransactionList(generics.ListCreateAPIView):
|
||||||
return Transaction.objects.filter(Q(payee=user) | Q(payer=user)).all()
|
return Transaction.objects.filter(Q(payee=user) | Q(payer=user)).all()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
user = self.request.user
|
payer = self.request.user
|
||||||
serializer.save(payee=user, application_client_id=get_application_client_id(self.request))
|
payee = self.request.auth.application.user
|
||||||
|
serializer.save(payer=payer, payee=payee, application_client_id=get_application_client_id(self.request))
|
||||||
|
|
||||||
|
|
||||||
class TransactionDetail(generics.RetrieveAPIView):
|
class TransactionDetail(generics.RetrieveAPIView):
|
||||||
|
|
|
||||||
190
client.py
Normal file
190
client.py
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
OAUTH_CLIENT_ID = 'qHhNuALPINvJ8UHAcBGsX1uVRWu3AmRMwRL5kVbi'
|
||||||
|
# OAUTH_CLIENT_SECRET = 'pbkdf2_sha256$390000$A8ru6iwhcjU1wEgPL6n6b8$GOhCcrfqw4Xkw6USpuEL6esjmNgqJe1A8q1Ec2dLxhw='
|
||||||
|
OAUTH_CLIENT_SECRET = 'pbkdf2_sha256$390000$A8ru6iwhcjU1wEgPL6n6b8$GOhCcrfqw4Xkw6USpuEL6esjmNgqJe1A8q1Ec2dLxhw='
|
||||||
|
API_URI = 'http://127.0.0.1:8000'
|
||||||
|
# API_URI = 'https://accounts.gooyal.com'
|
||||||
|
# NOTIFICATION_URI = 'https://notifications.gooyal.com'
|
||||||
|
# NOTIFICATION_URI = 'http://127.0.0.1:8001'
|
||||||
|
|
||||||
|
|
||||||
|
class ApiClient():
|
||||||
|
def __init__(self, phone_number, auth_data=None):
|
||||||
|
self.auth_data = {}
|
||||||
|
if auth_data:
|
||||||
|
self.auth_data = auth_data
|
||||||
|
|
||||||
|
self.phone_number = phone_number
|
||||||
|
|
||||||
|
def login(self, password):
|
||||||
|
phone_number = self.phone_number
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"grant_type": "password",
|
||||||
|
"username": phone_number,
|
||||||
|
"password": password,
|
||||||
|
# "scope": 'introspection',
|
||||||
|
"scope": 'introspection accounts.profile:list accounts.profile:retrieve accounts.account:retrieve accounts.account:update accounts.profile:inquiry accounts.account:change_password wallet.transaction:list wallet.invoice:create wallet.transaction:retrieve wallet.invoice:pay wallet.invoice:receipt wallet.deposit:submit wallet.deposit:verify wallet.withdraw:submit wallet.withdraw:verify ',
|
||||||
|
"auth_fields": 'phone_number:otp'
|
||||||
|
}
|
||||||
|
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
||||||
|
|
||||||
|
response = requests.post(f'{API_URI}/oauth2/token/',
|
||||||
|
data=data,
|
||||||
|
auth=auth)
|
||||||
|
|
||||||
|
auth_data = response.json()
|
||||||
|
if 'access_token' in auth_data:
|
||||||
|
self.auth_data = auth_data
|
||||||
|
|
||||||
|
return auth_data
|
||||||
|
|
||||||
|
def _request(self, path, data=None, files=None, method='get', with_auth=True, encode=True):
|
||||||
|
if files:
|
||||||
|
header = {}
|
||||||
|
else:
|
||||||
|
header = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"charset": "utf-8"
|
||||||
|
}
|
||||||
|
if with_auth:
|
||||||
|
access_token = self.auth_data.get('access_token', '')
|
||||||
|
header.setdefault("Authorization", f"Bearer {access_token}", )
|
||||||
|
|
||||||
|
if data and encode:
|
||||||
|
data = json.dumps(data)
|
||||||
|
|
||||||
|
response = requests.request(method, f'{API_URI}/{path}', headers=header, data=data, files=files)
|
||||||
|
try:
|
||||||
|
return response.json()
|
||||||
|
except:
|
||||||
|
return response.content.decode()
|
||||||
|
|
||||||
|
def request_otp(self):
|
||||||
|
phone_number = self.phone_number
|
||||||
|
|
||||||
|
path = 'users/api/request_otp/'
|
||||||
|
method = 'post'
|
||||||
|
data = {
|
||||||
|
'phone_number': phone_number
|
||||||
|
}
|
||||||
|
result = self._request(path=path, data=data, method=method, with_auth=False)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_account(self):
|
||||||
|
path = 'users/api/account/'
|
||||||
|
result = self._request(path=path)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def update_account(self, **kwargs):
|
||||||
|
path = 'users/api/account/'
|
||||||
|
data = {}
|
||||||
|
fields = (
|
||||||
|
'username',
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
'name',
|
||||||
|
'iban',
|
||||||
|
'iban_card_number',
|
||||||
|
'iban_account_number',
|
||||||
|
)
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if key in fields:
|
||||||
|
data.setdefault(key, value)
|
||||||
|
|
||||||
|
avatar = kwargs.get('avatar')
|
||||||
|
if avatar:
|
||||||
|
files = {'avatar': avatar}
|
||||||
|
encode = False
|
||||||
|
else:
|
||||||
|
files = None
|
||||||
|
encode = True
|
||||||
|
|
||||||
|
result = self._request(path=path, data=data, method='put', files=files, encode=encode)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def change_password(self, old_password, new_password, old_password_gateway='otp'):
|
||||||
|
path = 'users/api/change_password/'
|
||||||
|
data = {'old_password': old_password,
|
||||||
|
'new_password': new_password,
|
||||||
|
'old_password_gateway': old_password_gateway
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self._request(path=path, data=data, method='put')
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_user_profile(self, code):
|
||||||
|
path = f'users/api/users/{code}/'
|
||||||
|
result = self._request(path=path)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_transactions(self, page=None):
|
||||||
|
path = 'wallet/api/transactions/'
|
||||||
|
if page:
|
||||||
|
page = int(page)
|
||||||
|
path = f'{path}?page={page}'
|
||||||
|
|
||||||
|
return self._request(path=path)
|
||||||
|
|
||||||
|
def get_transaction(self, code):
|
||||||
|
path = f'transactions/{code}'
|
||||||
|
return self._request(path=path)
|
||||||
|
|
||||||
|
# create user invoice
|
||||||
|
# def create_invoice(self, amount, delay, payer=None):
|
||||||
|
# data = {
|
||||||
|
# 'delay': delay,
|
||||||
|
# 'amount': amount
|
||||||
|
# }
|
||||||
|
# if payer:
|
||||||
|
# data.setdefault('payer', {'code': payer})
|
||||||
|
# response = self._request('transactions/', method='post', data=data)
|
||||||
|
# return response and 'code' in response, response
|
||||||
|
|
||||||
|
# create application invoice
|
||||||
|
def create_invoice(self, amount, delay):
|
||||||
|
data = {
|
||||||
|
'delay': delay,
|
||||||
|
'amount': amount
|
||||||
|
}
|
||||||
|
response = self._request('transactions/', method='post', data=data)
|
||||||
|
return response and 'code' in response, response
|
||||||
|
|
||||||
|
def pay_transaction(self, code):
|
||||||
|
path = f'transactions/{code}/pay'
|
||||||
|
return self._request(path=path)
|
||||||
|
|
||||||
|
def receipt_transaction(self, code):
|
||||||
|
path = f'transactions/{code}/receipt'
|
||||||
|
return self._request(path=path)
|
||||||
|
|
||||||
|
def get_introspection(self):
|
||||||
|
path = 'oauth2/introspect/'
|
||||||
|
access_token = self.auth_data.get('access_token', '')
|
||||||
|
data = {
|
||||||
|
'token': access_token,
|
||||||
|
}
|
||||||
|
return self._request(path=path, data=data)
|
||||||
|
|
||||||
|
def notify(self):
|
||||||
|
data = {
|
||||||
|
'phone_number': "+989106853582",
|
||||||
|
'body': 'this is a test'
|
||||||
|
}
|
||||||
|
# data = json.dumps(data)
|
||||||
|
header = {
|
||||||
|
# "Content-Type": "application/json",
|
||||||
|
"charset": "utf-8"
|
||||||
|
}
|
||||||
|
access_token = self.auth_data.get('access_token', '')
|
||||||
|
# access_token = 'Px7WLUKoynua6qJq5IkIG8Fn5dPLXq'
|
||||||
|
header.setdefault("Authorization", f"Bearer {access_token}", )
|
||||||
|
|
||||||
|
response = requests.request('post', f'{NOTIFICATION_URI}/notifications/', headers=header, json=data)
|
||||||
|
try:
|
||||||
|
return response.json()
|
||||||
|
except:
|
||||||
|
return response.text
|
||||||
|
|
||||||
12
run.sh
12
run.sh
|
|
@ -1,9 +1,9 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
while ! nc -z $DB_HOST 3306 ; do
|
#while ! nc -z $DB_HOST 3306 ; do
|
||||||
echo "Waiting for the MySQL Server"
|
# echo "Waiting for the MySQL Server"
|
||||||
sleep 3
|
# sleep 3
|
||||||
done
|
#done
|
||||||
|
|
||||||
python3 manage.py collectstatic
|
#python3 manage.py collectstatic
|
||||||
python3 manage.py migrate
|
#python3 manage.py migrate
|
||||||
gunicorn accounts.wsgi:application --bind 0.0.0.0:8000 -w 4
|
gunicorn accounts.wsgi:application --bind 0.0.0.0:8000 -w 4
|
||||||
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% for transaction in object_list %}
|
{% for transaction in object_list %}
|
||||||
{{ transaction }}
|
{% if transaction.payee == request.user %}
|
||||||
|
دریافت مبلغ {{ transaction.amount }}از: {{ transaction.payer }} در وضعیت {{ transaction.get_state_display }}
|
||||||
|
{% else %}
|
||||||
|
پرداخت مبلغ {{ transaction.amount }} به: {{ transaction.payee }}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
Add table
Reference in a new issue