mirror of
https://github.com/OpenBankProject/API-Manager.git
synced 2026-02-06 14:26:53 +00:00
Ported obp app from API Tester #31
Also enables possibility to use DirectLogin and GatewayLogin
This commit is contained in:
parent
5fc4e45577
commit
2358d8eed6
10
README.md
10
README.md
@ -25,14 +25,8 @@ apimanager/
|
|||||||
│ ├── requirements.txt
|
│ ├── requirements.txt
|
||||||
│ └── supervisor.apimanager.conf
|
│ └── supervisor.apimanager.conf
|
||||||
├── db.sqlite3
|
├── db.sqlite3
|
||||||
├── logs [error opening dir]
|
├── logs
|
||||||
├── static-collected
|
├── static-collected
|
||||||
│ ├── admin
|
|
||||||
│ ├── consumers
|
|
||||||
│ ├── css
|
|
||||||
│ ├── img
|
|
||||||
│ ├── js
|
|
||||||
│ └── users
|
|
||||||
└── venv
|
└── venv
|
||||||
├── bin
|
├── bin
|
||||||
└── lib
|
└── lib
|
||||||
@ -54,7 +48,7 @@ Edit `apimanager/apimanager/local_settings.py`:
|
|||||||
# Used internally by Django, can be anything of your choice
|
# Used internally by Django, can be anything of your choice
|
||||||
SECRET_KEY = '<random string>'
|
SECRET_KEY = '<random string>'
|
||||||
# API hostname, e.g. https://api.openbankproject.com
|
# API hostname, e.g. https://api.openbankproject.com
|
||||||
OAUTH_API = '<hostname>'
|
API_HOST = '<hostname>'
|
||||||
# Consumer key + secret to authenticate the _app_ against the API
|
# Consumer key + secret to authenticate the _app_ against the API
|
||||||
OAUTH_CONSUMER_KEY = '<key>'
|
OAUTH_CONSUMER_KEY = '<key>'
|
||||||
OAUTH_CONSUMER_SECRET = '<secret>'
|
OAUTH_CONSUMER_SECRET = '<secret>'
|
||||||
|
|||||||
@ -45,7 +45,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'base',
|
'base',
|
||||||
'oauth',
|
'obp',
|
||||||
'consumers',
|
'consumers',
|
||||||
'users',
|
'users',
|
||||||
'customers',
|
'customers',
|
||||||
@ -187,13 +187,18 @@ API_DATETIMEFORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
|||||||
API_DATEFORMAT = '%Y-%m-%d'
|
API_DATEFORMAT = '%Y-%m-%d'
|
||||||
|
|
||||||
|
|
||||||
OAUTH_API = 'http://127.0.0.1:8080'
|
API_HOST = 'http://127.0.0.1:8080'
|
||||||
|
API_BASE_PATH = '/obp/v3.0.0'
|
||||||
|
# For some reason, swagger is not available at the latest API version
|
||||||
|
API_SWAGGER_BASE_PATH = '/obp/v1.4.0'
|
||||||
|
|
||||||
|
|
||||||
|
# Always save session$
|
||||||
|
SESSION_SAVE_EVERY_REQUEST = True
|
||||||
|
|
||||||
OAUTH_TOKEN_PATH = '/oauth/initiate'
|
OAUTH_TOKEN_PATH = '/oauth/initiate'
|
||||||
OAUTH_AUTHORIZATION_PATH = '/oauth/authorize'
|
OAUTH_AUTHORIZATION_PATH = '/oauth/authorize'
|
||||||
OAUTH_ACCESS_TOKEN_PATH = '/oauth/token'
|
OAUTH_ACCESS_TOKEN_PATH = '/oauth/token'
|
||||||
OAUTH_API_BASE_PATH = '/obp/v3.0.0'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Set OAuth client key/secret in apimanager/local_settings.py
|
# Set OAuth client key/secret in apimanager/local_settings.py
|
||||||
OAUTH_CONSUMER_KEY = None
|
OAUTH_CONSUMER_KEY = None
|
||||||
|
|||||||
@ -6,11 +6,19 @@ URLs for apimanager
|
|||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
from base.views import HomeView
|
from base.views import HomeView
|
||||||
|
from obp.views import OAuthInitiateView, OAuthAuthorizeView, LogoutView
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', HomeView.as_view(), name="home"),
|
url(r'^$', HomeView.as_view(), name="home"),
|
||||||
url(r'^oauth/', include('oauth.urls')),
|
# Defining authentication URLs here and not including oauth.urls for
|
||||||
|
# backward compatibility
|
||||||
|
url(r'^oauth/initiate$',
|
||||||
|
OAuthInitiateView.as_view(), name='oauth-initiate'),
|
||||||
|
url(r'^oauth/authorize$',
|
||||||
|
OAuthAuthorizeView.as_view(), name='oauth-authorize'),
|
||||||
|
url(r'^logout$',
|
||||||
|
LogoutView.as_view(), name='oauth-logout'),
|
||||||
url(r'^consumers/', include('consumers.urls')),
|
url(r'^consumers/', include('consumers.urls')),
|
||||||
url(r'^users/', include('users.urls')),
|
url(r'^users/', include('users.urls')),
|
||||||
url(r'^customers/', include('customers.urls')),
|
url(r'^customers/', include('customers.urls')),
|
||||||
|
|||||||
@ -1,116 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Module to handle the OBP API
|
|
||||||
|
|
||||||
It instantiates a convenience api object for imports, e.g.:
|
|
||||||
from base.api import api
|
|
||||||
"""
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
|
|
||||||
from requests.exceptions import ConnectionError
|
|
||||||
from requests_oauthlib import OAuth1Session
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth import logout
|
|
||||||
|
|
||||||
|
|
||||||
DATE_FORMAT = '%d/%b/%Y %H:%M:%S'
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def log(level, message):
|
|
||||||
"""Logs a given message on a given level to log facility"""
|
|
||||||
now = datetime.now().strftime(DATE_FORMAT)
|
|
||||||
msg = '[{}] API: {}'.format(now, message)
|
|
||||||
LOGGER.log(level, msg)
|
|
||||||
|
|
||||||
|
|
||||||
class APIError(Exception):
|
|
||||||
"""Exception class for API errors"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class API(object):
|
|
||||||
"""Implements an interface to the OBP API"""
|
|
||||||
|
|
||||||
def get(self, request, urlpath=''):
|
|
||||||
"""Gets data from the API"""
|
|
||||||
return self.call(request, 'GET', urlpath)
|
|
||||||
|
|
||||||
def delete(self, request, urlpath):
|
|
||||||
"""Deletes data from the API"""
|
|
||||||
return self.call(request, 'DELETE', urlpath)
|
|
||||||
|
|
||||||
def post(self, request, urlpath, payload):
|
|
||||||
"""Posts data to the API"""
|
|
||||||
return self.call(request, 'POST', urlpath, payload)
|
|
||||||
|
|
||||||
def put(self, request, urlpath, payload):
|
|
||||||
"""Puts data onto the API"""
|
|
||||||
return self.call(request, 'PUT', urlpath, payload)
|
|
||||||
|
|
||||||
def handle_response_404(self, response, prefix):
|
|
||||||
# Stripping HTML body ...
|
|
||||||
if response.text.find('body'):
|
|
||||||
msg = response.text.split('<body>')[1].split('</body>')[0]
|
|
||||||
msg = '{} {}: {}'.format(
|
|
||||||
prefix, response.status_code, msg)
|
|
||||||
log(logging.ERROR, msg)
|
|
||||||
raise APIError(msg)
|
|
||||||
|
|
||||||
def handle_response_500(self, response, prefix):
|
|
||||||
msg = '{} {}: {}'.format(
|
|
||||||
prefix, response.status_code, response.text)
|
|
||||||
log(logging.ERROR, msg)
|
|
||||||
raise APIError(msg)
|
|
||||||
|
|
||||||
def handle_response_error(self, request, prefix, error):
|
|
||||||
if 'Invalid or expired access token' in error:
|
|
||||||
logout(request)
|
|
||||||
msg = '{} {}'.format(prefix, error)
|
|
||||||
raise APIError(msg)
|
|
||||||
|
|
||||||
def handle_response(self, request, response):
|
|
||||||
"""Handles the response, e.g. errors or conversion to JSON"""
|
|
||||||
prefix = 'APIError'
|
|
||||||
if response.status_code == 404:
|
|
||||||
self.handle_response_404(response, prefix)
|
|
||||||
elif response.status_code == 500:
|
|
||||||
self.handle_response_500(response, prefix)
|
|
||||||
elif response.status_code in [204]:
|
|
||||||
return response.text
|
|
||||||
else:
|
|
||||||
data = response.json()
|
|
||||||
if 'error' in data:
|
|
||||||
self.handle_response_error(request, prefix, data['error'])
|
|
||||||
return data
|
|
||||||
|
|
||||||
def call(self, request, method='GET', urlpath='', payload=None):
|
|
||||||
"""Workhorse which actually calls the API"""
|
|
||||||
url = settings.OAUTH_API + settings.OAUTH_API_BASE_PATH + urlpath
|
|
||||||
log(logging.INFO, '{} {}'.format(method, url))
|
|
||||||
if payload:
|
|
||||||
log(logging.INFO, 'Payload: {}'.format(payload))
|
|
||||||
if not hasattr(request, 'api'):
|
|
||||||
request.api = OAuth1Session(
|
|
||||||
settings.OAUTH_CONSUMER_KEY,
|
|
||||||
client_secret=settings.OAUTH_CONSUMER_SECRET,
|
|
||||||
resource_owner_key=request.session['oauth_token'],
|
|
||||||
resource_owner_secret=request.session['oauth_secret']
|
|
||||||
)
|
|
||||||
time_start = time.time()
|
|
||||||
try:
|
|
||||||
if payload:
|
|
||||||
response = request.api.request(method, url, json=payload)
|
|
||||||
else:
|
|
||||||
response = request.api.request(method, url)
|
|
||||||
except ConnectionError as err:
|
|
||||||
raise APIError(err)
|
|
||||||
time_end = time.time()
|
|
||||||
elapsed = int((time_end - time_start) * 1000)
|
|
||||||
log(logging.INFO, 'Took {} ms'.format(elapsed))
|
|
||||||
return self.handle_response(request, response)
|
|
||||||
api = API()
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Helpers to reuse common API calls
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.contrib import messages
|
|
||||||
|
|
||||||
from .api import api, APIError
|
|
||||||
|
|
||||||
|
|
||||||
def get_bank_id_choices(request):
|
|
||||||
"""Gets a list of bank ids and bank ids as used by form choices"""
|
|
||||||
choices = [('', 'Choose ...')]
|
|
||||||
try:
|
|
||||||
result = api.get(request, '/banks')
|
|
||||||
for bank in result['banks']:
|
|
||||||
choices.append((bank['id'], bank['id']))
|
|
||||||
except APIError as err:
|
|
||||||
messages.error(request, err)
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_id_choices(request):
|
|
||||||
"""Gets a list of user ids and usernames as used by form choices"""
|
|
||||||
choices = [('', 'Choose ...')]
|
|
||||||
try:
|
|
||||||
result = api.get(request, '/users')
|
|
||||||
for user in result['users']:
|
|
||||||
choices.append((user['user_id'], user['username']))
|
|
||||||
except APIError as err:
|
|
||||||
messages.error(request, err)
|
|
||||||
return choices
|
|
||||||
@ -6,13 +6,13 @@ Context processors for base app
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
|
||||||
from base.api import api, APIError
|
from obp.api import API, APIError
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def api_root(request):
|
def api_root(request):
|
||||||
"""Returns the configured API_ROOT"""
|
"""Returns the configured API_ROOT"""
|
||||||
return {'API_ROOT': settings.OAUTH_API + settings.OAUTH_API_BASE_PATH}
|
return {'API_ROOT': settings.API_HOST + settings.API_BASE_PATH}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -21,7 +21,8 @@ def api_username(request):
|
|||||||
username = 'not authenticated'
|
username = 'not authenticated'
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
try:
|
try:
|
||||||
data = api.get(request, '/users/current')
|
api = API(request.session.get('obp'))
|
||||||
|
data = api.get('/users/current')
|
||||||
username = data['username']
|
username = data['username']
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
messages.error(request, err)
|
messages.error(request, err)
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<div class="well" id="intro">
|
<div class="well" id="intro">
|
||||||
<p>
|
<p>
|
||||||
This app gives you access to management functionality for the sandbox at <a href="{{ OAUTH_API }}">{{ OAUTH_API }}</a>. You have to <a href="{{ OAUTH_API }}/user_mgt/sign_up" title="Register at {{ OAUTH_API }}">register</a> an account before being able to proceed. The logged-in user needs to have specific roles granted to use the functionality.
|
This app gives you access to management functionality for the sandbox at <a href="{{ API_HOST }}">{{ API_HOST }}</a>. You have to <a href="{{ API_HOST }}/user_mgt/sign_up" title="Register at {{ API_HOST }}">register</a> an account before being able to proceed. The logged-in user needs to have specific roles granted to use the functionality.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -13,5 +13,5 @@ class HomeView(TemplateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(HomeView, self).get_context_data(**kwargs)
|
context = super(HomeView, self).get_context_data(**kwargs)
|
||||||
context['OAUTH_API'] = settings.OAUTH_API
|
context['API_HOST'] = settings.API_HOST
|
||||||
return context
|
return context
|
||||||
|
|||||||
@ -9,9 +9,7 @@ from django.contrib import messages
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic import FormView, TemplateView, View
|
from django.views.generic import FormView, TemplateView, View
|
||||||
|
|
||||||
from base.api import api, APIError
|
from obp.api import API, APIError
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IndexView(LoginRequiredMixin, TemplateView):
|
class IndexView(LoginRequiredMixin, TemplateView):
|
||||||
@ -20,9 +18,10 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(IndexView, self).get_context_data(**kwargs)
|
context = super(IndexView, self).get_context_data(**kwargs)
|
||||||
|
api = API(self.request.session.get('obp'))
|
||||||
try:
|
try:
|
||||||
urlpath = '/config'
|
urlpath = '/config'
|
||||||
config = api.get(self.request, urlpath)
|
config = api.get(urlpath)
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
messages.error(self.request, err)
|
messages.error(self.request, err)
|
||||||
config = {}
|
config = {}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.generic import TemplateView, RedirectView
|
from django.views.generic import TemplateView, RedirectView
|
||||||
|
|
||||||
from base.api import api, APIError
|
from obp.api import API, APIError
|
||||||
from base.filters import BaseFilter, FilterTime
|
from base.filters import BaseFilter, FilterTime
|
||||||
|
|
||||||
|
|
||||||
@ -68,10 +68,10 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(IndexView, self).get_context_data(**kwargs)
|
context = super(IndexView, self).get_context_data(**kwargs)
|
||||||
consumers = []
|
consumers = []
|
||||||
|
api = API(self.request.session.get('obp'))
|
||||||
try:
|
try:
|
||||||
urlpath = '/management/consumers'
|
urlpath = '/management/consumers'
|
||||||
consumers = api.get(self.request, urlpath)
|
consumers = api.get(urlpath)
|
||||||
consumers = FilterEnabled(context, self.request.GET)\
|
consumers = FilterEnabled(context, self.request.GET)\
|
||||||
.apply(consumers['list'])
|
.apply(consumers['list'])
|
||||||
consumers = FilterAppType(context, self.request.GET)\
|
consumers = FilterAppType(context, self.request.GET)\
|
||||||
@ -98,10 +98,11 @@ class DetailView(LoginRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(DetailView, self).get_context_data(**kwargs)
|
context = super(DetailView, self).get_context_data(**kwargs)
|
||||||
|
api = API(self.request.session.get('obp'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
urlpath = '/management/consumers/{}'.format(kwargs['consumer_id'])
|
urlpath = '/management/consumers/{}'.format(kwargs['consumer_id'])
|
||||||
consumer = api.get(self.request, urlpath)
|
consumer = api.get(urlpath)
|
||||||
consumer['created'] = datetime.strptime(
|
consumer['created'] = datetime.strptime(
|
||||||
consumer['created'], settings.API_DATETIMEFORMAT)
|
consumer['created'], settings.API_DATETIMEFORMAT)
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
@ -120,10 +121,11 @@ class EnableDisableView(LoginRequiredMixin, RedirectView):
|
|||||||
success = None
|
success = None
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
api = API(self.request.session.get('obp'))
|
||||||
try:
|
try:
|
||||||
urlpath = '/management/consumers/{}'.format(kwargs['consumer_id'])
|
urlpath = '/management/consumers/{}'.format(kwargs['consumer_id'])
|
||||||
payload = {'enabled': self.enabled}
|
payload = {'enabled': self.enabled}
|
||||||
api.put(self.request, urlpath, payload)
|
api.put(urlpath, payload)
|
||||||
messages.success(self.request, self.success)
|
messages.success(self.request, self.success)
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
messages.error(self.request, err)
|
messages.error(self.request, err)
|
||||||
|
|||||||
@ -12,8 +12,7 @@ from django.http import HttpResponseRedirect
|
|||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
|
|
||||||
from base.api import api, APIError
|
from obp.api import API, APIError
|
||||||
from base.api_helper import get_bank_id_choices, get_user_id_choices
|
|
||||||
|
|
||||||
from .forms import CreateCustomerForm
|
from .forms import CreateCustomerForm
|
||||||
|
|
||||||
@ -24,53 +23,56 @@ class CreateView(LoginRequiredMixin, FormView):
|
|||||||
template_name = 'customers/create.html'
|
template_name = 'customers/create.html'
|
||||||
success_url = reverse_lazy('customers-create')
|
success_url = reverse_lazy('customers-create')
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
self.api = API(request.session.get('obp'))
|
||||||
|
return super(CreateView, self).dispatch(request, *args,**kwargs)
|
||||||
|
|
||||||
def get_form(self, *args, **kwargs):
|
def get_form(self, *args, **kwargs):
|
||||||
form = super(CreateView, self).get_form(*args, **kwargs)
|
form = super(CreateView, self).get_form(*args, **kwargs)
|
||||||
fields = form.fields
|
fields = form.fields
|
||||||
fields['bank_id'].choices = get_bank_id_choices(self.request)
|
fields['bank_id'].choices = self.api.get_bank_id_choices()
|
||||||
fields['user_id'].choices = get_user_id_choices(self.request)
|
fields['user_id'].choices = self.api.get_user_id_choices()
|
||||||
fields['last_ok_date'].initial =\
|
fields['last_ok_date'].initial =\
|
||||||
datetime.datetime.now().strftime(settings.API_DATETIMEFORMAT)
|
datetime.datetime.now().strftime(settings.API_DATETIMEFORMAT)
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
data = form.cleaned_data
|
||||||
|
urlpath = '/banks/{}/customers'.format(data['bank_id'])
|
||||||
|
payload = {
|
||||||
|
'user_id': data['user_id'],
|
||||||
|
'customer_number': data['customer_number'],
|
||||||
|
'legal_name': data['legal_name'],
|
||||||
|
'mobile_phone_number': data['mobile_phone_number'],
|
||||||
|
'email': data['email'],
|
||||||
|
'face_image': {
|
||||||
|
'url': data['face_image_url'],
|
||||||
|
'date': data['face_image_date'],
|
||||||
|
},
|
||||||
|
'date_of_birth': data['date_of_birth'],
|
||||||
|
'relationship_status': data['relationship_status'],
|
||||||
|
'dependants': data['dependants'],
|
||||||
|
'dob_of_dependants': data['dob_of_dependants'],
|
||||||
|
'credit_rating': {
|
||||||
|
'rating': data['credit_rating_rating'],
|
||||||
|
'source': data['credit_rating_source'],
|
||||||
|
},
|
||||||
|
'credit_limit': {
|
||||||
|
'currency': data['credit_limit_currency'],
|
||||||
|
'amount': data['credit_limit_amount'],
|
||||||
|
},
|
||||||
|
'highest_education_attained':
|
||||||
|
data['highest_education_attained'],
|
||||||
|
'employment_status': data['employment_status'],
|
||||||
|
'kyc_status': data['kyc_status'],
|
||||||
|
'last_ok_date':
|
||||||
|
data['last_ok_date'].strftime(settings.API_DATETIMEFORMAT),
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
data = form.cleaned_data
|
result = self.api.post(urlpath, payload=payload)
|
||||||
urlpath = '/banks/{}/customers'.format(data['bank_id'])
|
|
||||||
payload = {
|
|
||||||
'user_id': data['user_id'],
|
|
||||||
'customer_number': data['customer_number'],
|
|
||||||
'legal_name': data['legal_name'],
|
|
||||||
'mobile_phone_number': data['mobile_phone_number'],
|
|
||||||
'email': data['email'],
|
|
||||||
'face_image': {
|
|
||||||
'url': data['face_image_url'],
|
|
||||||
'date': data['face_image_date'],
|
|
||||||
},
|
|
||||||
'date_of_birth': data['date_of_birth'],
|
|
||||||
'relationship_status': data['relationship_status'],
|
|
||||||
'dependants': data['dependants'],
|
|
||||||
'dob_of_dependants': data['dob_of_dependants'],
|
|
||||||
'credit_rating': {
|
|
||||||
'rating': data['credit_rating_rating'],
|
|
||||||
'source': data['credit_rating_source'],
|
|
||||||
},
|
|
||||||
'credit_limit': {
|
|
||||||
'currency': data['credit_limit_currency'],
|
|
||||||
'amount': data['credit_limit_amount'],
|
|
||||||
},
|
|
||||||
'highest_education_attained':
|
|
||||||
data['highest_education_attained'],
|
|
||||||
'employment_status': data['employment_status'],
|
|
||||||
'kyc_status': data['kyc_status'],
|
|
||||||
'last_ok_date':
|
|
||||||
data['last_ok_date'].strftime(settings.API_DATETIMEFORMAT),
|
|
||||||
}
|
|
||||||
result = api.post(self.request, urlpath, payload=payload)
|
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
messages.error(self.request, err)
|
messages.error(self.request, err)
|
||||||
return super(CreateView, self).form_invalid(form)
|
return super(CreateView, self).form_invalid(form)
|
||||||
|
|
||||||
msg = 'Customer number {} has been created successfully!'.format(
|
msg = 'Customer number {} has been created successfully!'.format(
|
||||||
result['customer_number'])
|
result['customer_number'])
|
||||||
messages.success(self.request, msg)
|
messages.success(self.request, msg)
|
||||||
|
|||||||
@ -15,7 +15,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.views.generic import FormView, TemplateView
|
from django.views.generic import FormView, TemplateView
|
||||||
from django.utils.http import urlquote
|
from django.utils.http import urlquote
|
||||||
|
|
||||||
from base.api import api, APIError
|
from obp.api import API, APIError
|
||||||
|
|
||||||
from .forms import APIMetricsForm, ConnectorMetricsForm
|
from .forms import APIMetricsForm, ConnectorMetricsForm
|
||||||
|
|
||||||
@ -117,8 +117,9 @@ class MetricsView(LoginRequiredMixin, TemplateView):
|
|||||||
metrics = []
|
metrics = []
|
||||||
params = self.to_api(cleaned_data)
|
params = self.to_api(cleaned_data)
|
||||||
urlpath = '{}?{}'.format(self.api_urlpath, params)
|
urlpath = '{}?{}'.format(self.api_urlpath, params)
|
||||||
|
api = API(self.request.session.get('obp'))
|
||||||
try:
|
try:
|
||||||
metrics = api.get(self.request, urlpath)
|
metrics = api.get(urlpath)
|
||||||
metrics = self.to_django(metrics['metrics'])
|
metrics = self.to_django(metrics['metrics'])
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
messages.error(self.request, err)
|
messages.error(self.request, err)
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
App config for OAuth 1 app
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class OauthConfig(AppConfig):
|
|
||||||
"""Config for OAuth 1"""
|
|
||||||
name = 'oauth'
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
URLs for OAuth 1 app
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
from .views import InitiateView, AuthorizeView, LogoutView
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^initiate$', InitiateView.as_view(), name='oauth-initiate'),
|
|
||||||
url(r'^authorize$', AuthorizeView.as_view(), name='oauth-authorize'),
|
|
||||||
url(r'^logout$', LogoutView.as_view(), name='oauth-logout'),
|
|
||||||
]
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Views for OAuth 1 app
|
|
||||||
"""
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth import login, logout
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.views.generic import RedirectView
|
|
||||||
|
|
||||||
from requests_oauthlib import OAuth1Session
|
|
||||||
from requests_oauthlib.oauth1_session import TokenRequestDenied
|
|
||||||
|
|
||||||
from base.api import api
|
|
||||||
|
|
||||||
|
|
||||||
class InitiateView(RedirectView):
|
|
||||||
"""View to initiate OAuth 1 session"""
|
|
||||||
|
|
||||||
def get_callback_uri(self, request):
|
|
||||||
"""
|
|
||||||
Gets the callback URI to where the user shall be returned after
|
|
||||||
authorization at OAuth 1 server
|
|
||||||
"""
|
|
||||||
base_url = '{}://{}'.format(
|
|
||||||
request.scheme, request.environ['HTTP_HOST'])
|
|
||||||
uri = base_url + reverse('oauth-authorize')
|
|
||||||
if 'next' in request.GET:
|
|
||||||
uri = '{}?next={}'.format(uri, request.GET['next'])
|
|
||||||
return uri
|
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
|
||||||
callback_uri = self.get_callback_uri(self.request)
|
|
||||||
session = OAuth1Session(
|
|
||||||
settings.OAUTH_CONSUMER_KEY,
|
|
||||||
client_secret=settings.OAUTH_CONSUMER_SECRET,
|
|
||||||
callback_uri=callback_uri,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
url = settings.OAUTH_API + settings.OAUTH_TOKEN_PATH
|
|
||||||
response = session.fetch_request_token(url)
|
|
||||||
except (ValueError, TokenRequestDenied) as err:
|
|
||||||
messages.error(self.request, err)
|
|
||||||
return reverse('home')
|
|
||||||
|
|
||||||
url = settings.OAUTH_API + settings.OAUTH_AUTHORIZATION_PATH
|
|
||||||
authorization_url = session.authorization_url(url)
|
|
||||||
self.request.session['oauth_token'] = response.get('oauth_token')
|
|
||||||
self.request.session['oauth_secret'] = response.get('oauth_token_secret')
|
|
||||||
self.request.session.modified = True
|
|
||||||
return authorization_url
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorizeView(RedirectView):
|
|
||||||
"""View to authorize user"""
|
|
||||||
|
|
||||||
def login_to_django(self):
|
|
||||||
"""
|
|
||||||
Logs the user into Django
|
|
||||||
Kind of faking it to establish if a user is authenticated later on
|
|
||||||
"""
|
|
||||||
data = api.get(self.request, '/users/current')
|
|
||||||
userid = data['user_id'] or data['email']
|
|
||||||
username = hashlib.sha256(userid.encode('utf-8')).hexdigest()
|
|
||||||
password = username
|
|
||||||
user, _ = User.objects.get_or_create(
|
|
||||||
username=username, password=password,
|
|
||||||
)
|
|
||||||
login(self.request, user)
|
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
|
||||||
session = OAuth1Session(
|
|
||||||
settings.OAUTH_CONSUMER_KEY,
|
|
||||||
settings.OAUTH_CONSUMER_SECRET,
|
|
||||||
resource_owner_key=self.request.session.get('oauth_token'),
|
|
||||||
resource_owner_secret=self.request.session.get('oauth_secret')
|
|
||||||
)
|
|
||||||
session.parse_authorization_response(self.request.build_absolute_uri())
|
|
||||||
url = settings.OAUTH_API + settings.OAUTH_ACCESS_TOKEN_PATH
|
|
||||||
try:
|
|
||||||
response = session.fetch_access_token(url)
|
|
||||||
self.request.session['oauth_token'] = response.get('oauth_token')
|
|
||||||
self.request.session['oauth_secret'] = response.get('oauth_token_secret')
|
|
||||||
self.request.session.modified = True
|
|
||||||
self.login_to_django()
|
|
||||||
except TokenRequestDenied as err:
|
|
||||||
messages.error(self.request, err)
|
|
||||||
redirect_url = self.request.GET.get('next', reverse('home'))
|
|
||||||
return redirect_url
|
|
||||||
|
|
||||||
|
|
||||||
class LogoutView(RedirectView):
|
|
||||||
"""View to logout"""
|
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
|
||||||
logout(self.request)
|
|
||||||
return reverse('home')
|
|
||||||
175
apimanager/obp/api.py
Normal file
175
apimanager/obp/api.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Module to handle the OBP API
|
||||||
|
|
||||||
|
It instantiates a convenience api object for imports, e.g.:
|
||||||
|
from obp.api import api
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
DATE_FORMAT = '%d/%b/%Y %H:%M:%S'
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def log(level, message):
|
||||||
|
"""Logs a given message on a given level to log facility"""
|
||||||
|
now = datetime.now().strftime(DATE_FORMAT)
|
||||||
|
msg = '[{}] API: {}'.format(now, message)
|
||||||
|
LOGGER.log(level, msg)
|
||||||
|
|
||||||
|
|
||||||
|
class APIError(Exception):
|
||||||
|
"""Exception class for API errors"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class API(object):
|
||||||
|
"""Implements an interface to the OBP API"""
|
||||||
|
session = None
|
||||||
|
swagger = None
|
||||||
|
|
||||||
|
def __init__(self, session_data=None):
|
||||||
|
self.set_base_path()
|
||||||
|
if session_data:
|
||||||
|
self.start_session(session_data)
|
||||||
|
self.session_data = session_data
|
||||||
|
|
||||||
|
def set_base_path(self, base_path=None):
|
||||||
|
"""Sets the basepath for API calls"""
|
||||||
|
if base_path:
|
||||||
|
self.base_path = settings.API_HOST + base_path
|
||||||
|
else:
|
||||||
|
self.base_path = settings.API_HOST + settings.API_BASE_PATH
|
||||||
|
|
||||||
|
def call(self, method='GET', urlpath='', payload=None):
|
||||||
|
"""Workhorse which actually calls the API"""
|
||||||
|
url = self.base_path + urlpath
|
||||||
|
log(logging.INFO, '{} {}'.format(method, url))
|
||||||
|
if payload:
|
||||||
|
log(logging.INFO, 'Payload: {}'.format(payload))
|
||||||
|
# use `requests` if no session has been started
|
||||||
|
session = self.session or requests
|
||||||
|
time_start = time.time()
|
||||||
|
try:
|
||||||
|
if payload:
|
||||||
|
response = session.request(method, url, json=payload)
|
||||||
|
else:
|
||||||
|
response = session.request(method, url)
|
||||||
|
except ConnectionError as err:
|
||||||
|
raise APIError(err)
|
||||||
|
time_end = time.time()
|
||||||
|
elapsed = int((time_end - time_start) * 1000)
|
||||||
|
log(logging.INFO, 'Took {} ms'.format(elapsed))
|
||||||
|
response.execution_time = elapsed
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get(self, urlpath=''):
|
||||||
|
"""Gets data from the API"""
|
||||||
|
response = self.call('GET', urlpath)
|
||||||
|
return self.handle_response(response)
|
||||||
|
|
||||||
|
def delete(self, urlpath):
|
||||||
|
"""Deletes data from the API"""
|
||||||
|
response = self.call('DELETE', urlpath)
|
||||||
|
return self.handle_response(response)
|
||||||
|
|
||||||
|
def post(self, urlpath, payload):
|
||||||
|
"""Posts data to the API"""
|
||||||
|
response = self.call('POST', urlpath, payload)
|
||||||
|
return self.handle_response(response)
|
||||||
|
|
||||||
|
def put(self, urlpath, payload):
|
||||||
|
"""Puts data onto the API"""
|
||||||
|
response = self.call('PUT', urlpath, payload)
|
||||||
|
return self.handle_response(response)
|
||||||
|
|
||||||
|
def handle_response_404(self, response, prefix):
|
||||||
|
# Stripping HTML body ...
|
||||||
|
if response.text.find('body'):
|
||||||
|
msg = response.text.split('<body>')[1].split('</body>')[0]
|
||||||
|
msg = '{} {}: {}'.format(
|
||||||
|
prefix, response.status_code, msg)
|
||||||
|
log(logging.ERROR, msg)
|
||||||
|
raise APIError(msg)
|
||||||
|
|
||||||
|
def handle_response_500(self, response, prefix):
|
||||||
|
msg = '{} {}: {}'.format(
|
||||||
|
prefix, response.status_code, response.text)
|
||||||
|
log(logging.ERROR, msg)
|
||||||
|
raise APIError(msg)
|
||||||
|
|
||||||
|
def handle_response_error(self, prefix, error):
|
||||||
|
if 'Invalid or expired access token' in error:
|
||||||
|
raise APIError(error)
|
||||||
|
msg = '{} {}'.format(prefix, error)
|
||||||
|
raise APIError(msg)
|
||||||
|
|
||||||
|
def handle_response(self, response):
|
||||||
|
"""Handles the response, e.g. errors or conversion to JSON"""
|
||||||
|
prefix = 'APIError'
|
||||||
|
if response.status_code == 404:
|
||||||
|
self.handle_response_404(response, prefix)
|
||||||
|
elif response.status_code == 500:
|
||||||
|
self.handle_response_500(response, prefix)
|
||||||
|
elif response.status_code in [204]:
|
||||||
|
return response.text
|
||||||
|
else:
|
||||||
|
data = response.json()
|
||||||
|
if 'error' in data:
|
||||||
|
self.handle_response_error(prefix, data['error'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def start_session(self, session_data):
|
||||||
|
"""
|
||||||
|
Starts a session with given session_data:
|
||||||
|
- Authenticator class name (e.g. obp.oauth.OAuthAuthenticator)
|
||||||
|
- Token data
|
||||||
|
for subsequent requests to the API
|
||||||
|
"""
|
||||||
|
if 'authenticator' in session_data and\
|
||||||
|
'authenticator_kwargs' in session_data:
|
||||||
|
mod_name, cls_name = session_data['authenticator'].rsplit('.', 1)
|
||||||
|
log(logging.INFO, 'Authenticator {}'.format(cls_name))
|
||||||
|
cls = getattr(importlib.import_module(mod_name), cls_name)
|
||||||
|
authenticator = cls(**session_data['authenticator_kwargs'])
|
||||||
|
self.session = authenticator.get_session()
|
||||||
|
return self.session
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_swagger(self):
|
||||||
|
"""Gets the swagger definition from the API"""
|
||||||
|
if not self.session_data.get('swagger'):
|
||||||
|
self.set_base_path(settings.API_SWAGGER_BASE_PATH)
|
||||||
|
urlpath = '/resource-docs/v3.0.0/swagger'
|
||||||
|
response = self.get(urlpath)
|
||||||
|
# Set base path back
|
||||||
|
self.set_base_path()
|
||||||
|
self.session_data['swagger'] = response
|
||||||
|
return self.session_data.get('swagger')
|
||||||
|
|
||||||
|
def get_bank_id_choices(self):
|
||||||
|
"""Gets a list of bank ids and bank ids as used by form choices"""
|
||||||
|
choices = [('', 'Choose ...')]
|
||||||
|
result = self.get('/banks')
|
||||||
|
for bank in result['banks']:
|
||||||
|
choices.append((bank['id'], bank['id']))
|
||||||
|
return choices
|
||||||
|
|
||||||
|
def get_user_id_choices(self):
|
||||||
|
"""Gets a list of user ids and usernames as used by form choices"""
|
||||||
|
choices = [('', 'Choose ...')]
|
||||||
|
result = self.get('/users')
|
||||||
|
for user in result['users']:
|
||||||
|
choices.append((user['user_id'], user['username']))
|
||||||
|
return choices
|
||||||
11
apimanager/obp/apps.py
Normal file
11
apimanager/obp/apps.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
App config for OBP app
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class OBPConfig(AppConfig):
|
||||||
|
"""Config for OBP"""
|
||||||
|
name = 'obp'
|
||||||
20
apimanager/obp/authenticator.py
Normal file
20
apimanager/obp/authenticator.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Base authenticator for OBP app
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth import login
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticatorError(Exception):
|
||||||
|
"""Exception class for Authenticator errors"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Authenticator(object):
|
||||||
|
"""Generic authenticator to the API"""
|
||||||
|
pass
|
||||||
51
apimanager/obp/directlogin.py
Normal file
51
apimanager/obp/directlogin.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
DirectLogin authenticator for OBP app
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from .authenticator import Authenticator, AuthenticatorError
|
||||||
|
|
||||||
|
|
||||||
|
class DirectLoginAuthenticator(Authenticator):
|
||||||
|
"""Implements a DirectLogin authenticator to the API"""
|
||||||
|
|
||||||
|
token = None
|
||||||
|
|
||||||
|
def __init__(self, token=None):
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
def login_to_api(self, data):
|
||||||
|
"""
|
||||||
|
Logs into the API and returns the token
|
||||||
|
|
||||||
|
data is a dict which contains keys username, password and consumer_key
|
||||||
|
"""
|
||||||
|
url = settings.API_HOST + settings.DIRECTLOGIN_PATH
|
||||||
|
authorization = 'DirectLogin username="{}",password="{}",consumer_key="{}"'.format( # noqa
|
||||||
|
data['username'],
|
||||||
|
data['password'],
|
||||||
|
data['consumer_key'])
|
||||||
|
headers = {'Authorization': authorization}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
except requests.exceptions.ConnectionError as err:
|
||||||
|
raise AuthenticatorError(err)
|
||||||
|
|
||||||
|
result = response.json()
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise AuthenticatorError(result['error'])
|
||||||
|
else:
|
||||||
|
self.token = result['token']
|
||||||
|
|
||||||
|
def get_session(self):
|
||||||
|
"""Returns a session object to make authenticated requests"""
|
||||||
|
headers = {'Authorization': 'DirectLogin token={}'.format(self.token)}
|
||||||
|
session = requests.Session()
|
||||||
|
session.headers.update(headers)
|
||||||
|
return session
|
||||||
53
apimanager/obp/forms.py
Normal file
53
apimanager/obp/forms.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Forms for OBP app
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from .authenticator import AuthenticatorError
|
||||||
|
from .directlogin import DirectLoginAuthenticator
|
||||||
|
from .gatewaylogin import GatewayLoginAuthenticator
|
||||||
|
|
||||||
|
|
||||||
|
class DirectLoginForm(forms.Form):
|
||||||
|
username = forms.CharField(widget=forms.TextInput(
|
||||||
|
attrs={'class': 'form-control'}))
|
||||||
|
password = forms.CharField(widget=forms.PasswordInput(
|
||||||
|
attrs={'class': 'form-control'}))
|
||||||
|
consumer_key = forms.CharField(widget=forms.TextInput(
|
||||||
|
attrs={'class': 'form-control'}))
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""
|
||||||
|
Stores an authenticator in cleaned_data after successful login to API
|
||||||
|
"""
|
||||||
|
cleaned_data = super(DirectLoginForm, self).clean()
|
||||||
|
authenticator = DirectLoginAuthenticator()
|
||||||
|
try:
|
||||||
|
authenticator.login_to_api(cleaned_data)
|
||||||
|
cleaned_data['authenticator'] = authenticator
|
||||||
|
except AuthenticatorError as err:
|
||||||
|
raise forms.ValidationError(err)
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class GatewayLoginForm(forms.Form):
|
||||||
|
username = forms.CharField(widget=forms.TextInput(
|
||||||
|
attrs={'class': 'form-control'}))
|
||||||
|
secret = forms.CharField(widget=forms.PasswordInput(
|
||||||
|
attrs={'class': 'form-control'}))
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""
|
||||||
|
Stores an authenticator in cleaned_data after successful login to API
|
||||||
|
"""
|
||||||
|
cleaned_data = super(GatewayLoginForm, self).clean()
|
||||||
|
authenticator = GatewayLoginAuthenticator()
|
||||||
|
try:
|
||||||
|
authenticator.login_to_api(cleaned_data)
|
||||||
|
cleaned_data['authenticator'] = authenticator
|
||||||
|
except AuthenticatorError as err:
|
||||||
|
raise forms.ValidationError(err)
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
67
apimanager/obp/gatewaylogin.py
Normal file
67
apimanager/obp/gatewaylogin.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
GatewayLogin authenticator for OBP app
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import jwt
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from .authenticator import Authenticator, AuthenticatorError
|
||||||
|
|
||||||
|
|
||||||
|
class GatewayLoginAuthenticator(Authenticator):
|
||||||
|
"""Implements a GatewayLogin authenticator to the API"""
|
||||||
|
|
||||||
|
token = None
|
||||||
|
|
||||||
|
def __init__(self, token=None):
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
def create_jwt(self, data):
|
||||||
|
"""
|
||||||
|
Creates a JWT used for future requests tothe API
|
||||||
|
data is a dict which contains keys username, secret
|
||||||
|
"""
|
||||||
|
url = settings.API_HOST + settings.DIRECTLOGIN_PATH
|
||||||
|
message = {
|
||||||
|
'username': data['username'],
|
||||||
|
'timestamp': 'unused',
|
||||||
|
'consumer_id': '', # Do not create new consumer
|
||||||
|
'consumer_name': '', # Do not create new consumer
|
||||||
|
}
|
||||||
|
if settings.GATEWAYLOGIN_HAS_CBS:
|
||||||
|
# Not sure if that is the right thing to do
|
||||||
|
message['is_first'] = True
|
||||||
|
else:
|
||||||
|
# Fake when there is no core banking system
|
||||||
|
message.update({
|
||||||
|
'is_first': False,
|
||||||
|
'CBS_auth_token': 'dummy',
|
||||||
|
})
|
||||||
|
token = jwt.encode(message, data['secret'], 'HS256')
|
||||||
|
self.token = token.decode('utf-8')
|
||||||
|
return self.token
|
||||||
|
|
||||||
|
def login_to_api(self, data):
|
||||||
|
token = self.create_jwt(data)
|
||||||
|
# Make a test call to see if the token works
|
||||||
|
url = settings.API_HOST + settings.API_BASE_PATH + '/users/current'
|
||||||
|
api = self.get_session()
|
||||||
|
try:
|
||||||
|
response = api.get(url)
|
||||||
|
except requests.exceptions.ConnectionError as err:
|
||||||
|
raise AuthenticationError(err)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise AuthenticatorError(response.json()['error'])
|
||||||
|
else:
|
||||||
|
return token
|
||||||
|
|
||||||
|
def get_session(self):
|
||||||
|
"""Returns a session object to make authenticated requests"""
|
||||||
|
headers = {'Authorization': 'GatewayLogin token="{}"'.format(self.token)}
|
||||||
|
session = requests.Session()
|
||||||
|
session.headers.update(headers)
|
||||||
|
return session
|
||||||
77
apimanager/obp/oauth.py
Normal file
77
apimanager/obp/oauth.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OAuth authenticator for OBP app
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
from requests_oauthlib import OAuth1Session
|
||||||
|
from requests_oauthlib.oauth1_session import TokenRequestDenied
|
||||||
|
|
||||||
|
from .authenticator import Authenticator, AuthenticatorError
|
||||||
|
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OAuthAuthenticator(Authenticator):
|
||||||
|
"""Implements an OAuth authenticator to the API"""
|
||||||
|
|
||||||
|
token = None
|
||||||
|
secret = None
|
||||||
|
|
||||||
|
def __init__(self, token=None, secret=None):
|
||||||
|
self.token = token
|
||||||
|
self.secret = secret
|
||||||
|
|
||||||
|
def get_authorization_url(self, callback_uri):
|
||||||
|
session = OAuth1Session(
|
||||||
|
settings.OAUTH_CONSUMER_KEY,
|
||||||
|
client_secret=settings.OAUTH_CONSUMER_SECRET,
|
||||||
|
callback_uri=callback_uri,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
url = settings.API_HOST + settings.OAUTH_TOKEN_PATH
|
||||||
|
response = session.fetch_request_token(url)
|
||||||
|
except (ValueError, TokenRequestDenied, ConnectionError) as err:
|
||||||
|
raise AuthenticatorError(err)
|
||||||
|
else:
|
||||||
|
self.token = response.get('oauth_token')
|
||||||
|
self.secret = response.get('oauth_token_secret')
|
||||||
|
url = settings.API_HOST + settings.OAUTH_AUTHORIZATION_PATH
|
||||||
|
authorization_url = session.authorization_url(url)
|
||||||
|
LOGGER.log(logging.INFO, 'Initial token {}, secret {}'.format(
|
||||||
|
self.token, self.secret))
|
||||||
|
return authorization_url
|
||||||
|
|
||||||
|
def set_access_token(self, authorization_url):
|
||||||
|
session = OAuth1Session(
|
||||||
|
settings.OAUTH_CONSUMER_KEY,
|
||||||
|
settings.OAUTH_CONSUMER_SECRET,
|
||||||
|
resource_owner_key=self.token,
|
||||||
|
resource_owner_secret=self.secret,
|
||||||
|
)
|
||||||
|
session.parse_authorization_response(authorization_url)
|
||||||
|
url = settings.API_HOST + settings.OAUTH_ACCESS_TOKEN_PATH
|
||||||
|
try:
|
||||||
|
response = session.fetch_access_token(url)
|
||||||
|
except (TokenRequestDenied, ConnectionError) as err:
|
||||||
|
raise AuthenticatorError(err)
|
||||||
|
else:
|
||||||
|
self.token = response.get('oauth_token')
|
||||||
|
self.secret = response.get('oauth_token_secret')
|
||||||
|
LOGGER.log(logging.INFO, 'Updated token {}, secret {}'.format(
|
||||||
|
self.token, self.secret))
|
||||||
|
|
||||||
|
def get_session(self):
|
||||||
|
session = OAuth1Session(
|
||||||
|
settings.OAUTH_CONSUMER_KEY,
|
||||||
|
client_secret=settings.OAUTH_CONSUMER_SECRET,
|
||||||
|
resource_owner_key=self.token,
|
||||||
|
resource_owner_secret=self.secret,
|
||||||
|
)
|
||||||
|
return session
|
||||||
25
apimanager/obp/templates/obp/directlogin.html
Normal file
25
apimanager/obp/templates/obp/directlogin.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>DirectLogin</h1>
|
||||||
|
<form action="{% url 'directlogin' %}" method="post">
|
||||||
|
{% if form.non_field_errors %}<div class="alert alert-danger">{{ form.non_field_errors }}</div>{% endif %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
{% if form.username.errors %}<div class="alert alert-danger">{{ form.username.errors }}</div>{% endif %}
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
{{ form.username }}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
{% if form.password.errors %}<div class="alert alert-danger">{{ form.password.errors }}</div>{% endif %}
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
{{ form.password }}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
{% if form.consumer_key.errors %}<div class="alert alert-danger">{{ form.consumer_key.errors }}</div>{% endif %}
|
||||||
|
<label for="consumer-key">Consumer Key:</label>
|
||||||
|
{{ form.consumer_key }}
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary">Login</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
20
apimanager/obp/templates/obp/gatewaylogin.html
Normal file
20
apimanager/obp/templates/obp/gatewaylogin.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>GatewayLogin</h1>
|
||||||
|
<form action="{% url 'gatewaylogin' %}" method="post">
|
||||||
|
{% if form.non_field_errors %}<div class="alert alert-danger">{{ form.non_field_errors }}</div>{% endif %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
{% if form.username.errors %}<div class="alert alert-danger">{{ form.username.errors }}</div>{% endif %}
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
{{ form.username }}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
{% if form.secret.errors %}<div class="alert alert-danger">{{ form.secret.errors }}</div>{% endif %}
|
||||||
|
<label for="secret">Secret:</label>
|
||||||
|
{{ form.secret }}
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary">Login</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
27
apimanager/obp/urls.py
Normal file
27
apimanager/obp/urls.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
URLs for OBP app
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from .views import (
|
||||||
|
OAuthInitiateView, OAuthAuthorizeView,
|
||||||
|
DirectLoginView,
|
||||||
|
GatewayLoginView,
|
||||||
|
LogoutView,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^oauth/initiate$',
|
||||||
|
OAuthInitiateView.as_view(), name='oauth-initiate'),
|
||||||
|
url(r'^oauth/authorize$',
|
||||||
|
OAuthAuthorizeView.as_view(), name='oauth-authorize'),
|
||||||
|
url(r'^directlogin$',
|
||||||
|
DirectLoginView.as_view(), name='directlogin'),
|
||||||
|
url(r'^gatewaylogin$',
|
||||||
|
GatewayLoginView.as_view(), name='gatewaylogin'),
|
||||||
|
url(r'^logout$',
|
||||||
|
LogoutView.as_view(), name='oauth-logout'),
|
||||||
|
]
|
||||||
160
apimanager/obp/views.py
Normal file
160
apimanager/obp/views.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Views for OBP app
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth import login, logout
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views.generic import RedirectView, FormView
|
||||||
|
|
||||||
|
from .api import API, APIError
|
||||||
|
from .authenticator import AuthenticatorError
|
||||||
|
from .forms import DirectLoginForm, GatewayLoginForm
|
||||||
|
from .oauth import OAuthAuthenticator
|
||||||
|
|
||||||
|
|
||||||
|
class LoginToDjangoMixin(object):
|
||||||
|
"""Mixin to login to Django from views."""
|
||||||
|
def login_to_django(self):
|
||||||
|
"""
|
||||||
|
Logs the user into Django
|
||||||
|
Kind of faking it to establish if a user is authenticated later on
|
||||||
|
"""
|
||||||
|
api = API(self.request.session.get('obp'))
|
||||||
|
try:
|
||||||
|
data = api.get('/users/current')
|
||||||
|
except APIError as err:
|
||||||
|
messages.error(self.request, err)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
userid = data['user_id'] or data['email']
|
||||||
|
username = hashlib.sha256(userid.encode('utf-8')).hexdigest()
|
||||||
|
password = username
|
||||||
|
user, _ = User.objects.get_or_create(
|
||||||
|
username=username, password=password,
|
||||||
|
)
|
||||||
|
login(self.request, user)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class OAuthInitiateView(RedirectView):
|
||||||
|
"""View to initiate OAuth session"""
|
||||||
|
|
||||||
|
def get_callback_uri(self, request):
|
||||||
|
"""
|
||||||
|
Gets the callback URI to where the user shall be returned after
|
||||||
|
initiation at OAuth server
|
||||||
|
"""
|
||||||
|
base_url = '{}://{}'.format(
|
||||||
|
request.scheme, request.environ['HTTP_HOST'])
|
||||||
|
uri = base_url + reverse('oauth-authorize')
|
||||||
|
if 'next' in request.GET:
|
||||||
|
uri = '{}?next={}'.format(uri, request.GET['next'])
|
||||||
|
return uri
|
||||||
|
|
||||||
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
callback_uri = self.get_callback_uri(self.request)
|
||||||
|
try:
|
||||||
|
authenticator = OAuthAuthenticator()
|
||||||
|
authorization_url = authenticator.get_authorization_url(
|
||||||
|
callback_uri)
|
||||||
|
except AuthenticatorError as err:
|
||||||
|
messages.error(self.request, err)
|
||||||
|
return reverse('home')
|
||||||
|
else:
|
||||||
|
self.request.session['obp'] = {
|
||||||
|
'authenticator': 'obp.oauth.OAuthAuthenticator',
|
||||||
|
'authenticator_kwargs': {
|
||||||
|
'token': authenticator.token,
|
||||||
|
'secret': authenticator.secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authorization_url
|
||||||
|
|
||||||
|
|
||||||
|
class OAuthAuthorizeView(RedirectView, LoginToDjangoMixin):
|
||||||
|
"""View to authorize user after OAuth 1 initiation"""
|
||||||
|
|
||||||
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
session_data = self.request.session.get('obp')
|
||||||
|
authenticator_kwargs = session_data.get('authenticator_kwargs')
|
||||||
|
authenticator = OAuthAuthenticator(**authenticator_kwargs)
|
||||||
|
authorization_url = self.request.build_absolute_uri()
|
||||||
|
try:
|
||||||
|
authenticator.set_access_token(authorization_url)
|
||||||
|
except AuthenticatorError as err:
|
||||||
|
messages.error(self.request, err)
|
||||||
|
else:
|
||||||
|
session_data['authenticator_kwargs'] = {
|
||||||
|
'token': authenticator.token,
|
||||||
|
'secret': authenticator.secret,
|
||||||
|
}
|
||||||
|
self.login_to_django()
|
||||||
|
messages.success(self.request, 'OAuth login successful!')
|
||||||
|
redirect_url = self.request.GET.get('next', reverse('home'))
|
||||||
|
return redirect_url
|
||||||
|
|
||||||
|
|
||||||
|
class DirectLoginView(FormView, LoginToDjangoMixin):
|
||||||
|
"""View to login via DirectLogin"""
|
||||||
|
form_class = DirectLoginForm
|
||||||
|
template_name = 'obp/directlogin.html'
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
messages.success(self.request, 'DirectLogin successful!')
|
||||||
|
return reverse('runtests-index')
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""
|
||||||
|
Stores a DirectLogin token in the request's session for use in
|
||||||
|
future requests. It also logs in to Django.
|
||||||
|
"""
|
||||||
|
authenticator = form.cleaned_data['authenticator']
|
||||||
|
self.request.session['obp'] = {
|
||||||
|
'authenticator': 'obp.directlogin.DirectLoginAuthenticator',
|
||||||
|
'authenticator_kwargs': {
|
||||||
|
'token': authenticator.token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.login_to_django()
|
||||||
|
return super(DirectLoginView, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class GatewayLoginView(FormView, LoginToDjangoMixin):
|
||||||
|
"""View to login via GatewayLogin"""
|
||||||
|
form_class = GatewayLoginForm
|
||||||
|
template_name = 'obp/gatewaylogin.html'
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
messages.success(self.request, 'GatewayLogin successful!')
|
||||||
|
return reverse('runtests-index')
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""
|
||||||
|
Stores a GatewayLogin token in the request's session for use in
|
||||||
|
future requests. It also logs in to Django.
|
||||||
|
"""
|
||||||
|
authenticator = form.cleaned_data['authenticator']
|
||||||
|
self.request.session['obp'] = {
|
||||||
|
'authenticator': 'obp.gatewaylogin.GatewayLoginAuthenticator',
|
||||||
|
'authenticator_kwargs': {
|
||||||
|
'token': authenticator.token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.login_to_django()
|
||||||
|
return super(GatewayLoginView, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class LogoutView(RedirectView):
|
||||||
|
"""View to logout"""
|
||||||
|
|
||||||
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
logout(self.request)
|
||||||
|
if 'obp' in self.request.session:
|
||||||
|
del self.request.session['obp']
|
||||||
|
return reverse('home')
|
||||||
@ -10,8 +10,7 @@ from django.urls import reverse, reverse_lazy
|
|||||||
from django.views.generic import FormView, TemplateView, View
|
from django.views.generic import FormView, TemplateView, View
|
||||||
|
|
||||||
from base.filters import BaseFilter
|
from base.filters import BaseFilter
|
||||||
from base.api import api, APIError
|
from obp.api import API, APIError
|
||||||
from base.api_helper import get_bank_id_choices
|
|
||||||
|
|
||||||
from .forms import AddEntitlementForm
|
from .forms import AddEntitlementForm
|
||||||
|
|
||||||
@ -42,9 +41,10 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
def get_users_rolenames(self, context):
|
def get_users_rolenames(self, context):
|
||||||
users = []
|
users = []
|
||||||
|
api = API(self.request.session.get('obp'))
|
||||||
try:
|
try:
|
||||||
urlpath = '/users'
|
urlpath = '/users'
|
||||||
users = api.get(self.request, urlpath)
|
users = api.get(urlpath)
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
messages.error(self.request, err)
|
messages.error(self.request, err)
|
||||||
return [], []
|
return [], []
|
||||||
@ -85,9 +85,13 @@ class DetailView(LoginRequiredMixin, FormView):
|
|||||||
form_class = AddEntitlementForm
|
form_class = AddEntitlementForm
|
||||||
template_name = 'users/detail.html'
|
template_name = 'users/detail.html'
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
self.api = API(request.session.get('obp'))
|
||||||
|
return super(DetailView, self).dispatch(request, *args,**kwargs)
|
||||||
|
|
||||||
def get_form(self, *args, **kwargs):
|
def get_form(self, *args, **kwargs):
|
||||||
form = super(DetailView, self).get_form(*args, **kwargs)
|
form = super(DetailView, self).get_form(*args, **kwargs)
|
||||||
form.fields['bank_id'].choices = get_bank_id_choices(self.request)
|
form.fields['bank_id'].choices = self.api.get_bank_id_choices()
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@ -99,7 +103,7 @@ class DetailView(LoginRequiredMixin, FormView):
|
|||||||
'bank_id': data['bank_id'],
|
'bank_id': data['bank_id'],
|
||||||
'role_name': data['role_name'],
|
'role_name': data['role_name'],
|
||||||
}
|
}
|
||||||
entitlement = api.post(self.request, urlpath, payload=payload)
|
entitlement = self.api.post(urlpath, payload=payload)
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
messages.error(self.request, err)
|
messages.error(self.request, err)
|
||||||
return super(DetailView, self).form_invalid(form)
|
return super(DetailView, self).form_invalid(form)
|
||||||
@ -112,13 +116,12 @@ class DetailView(LoginRequiredMixin, FormView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(DetailView, self).get_context_data(**kwargs)
|
context = super(DetailView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
# NOTE: assuming there is just one user with that email address
|
# NOTE: assuming there is just one user with that email address
|
||||||
# The API needs a call 'get user by id'!
|
# The API needs a call 'get user by id'!
|
||||||
user = {}
|
user = {}
|
||||||
try:
|
try:
|
||||||
urlpath = '/users/user_id/{}'.format(self.kwargs['user_id'])
|
urlpath = '/users/user_id/{}'.format(self.kwargs['user_id'])
|
||||||
user = api.get(self.request, urlpath)
|
user = self.api.get(urlpath)
|
||||||
context['form'].fields['user_id'].initial = user['user_id']
|
context['form'].fields['user_id'].initial = user['user_id']
|
||||||
except APIError as err:
|
except APIError as err:
|
||||||
messages.error(self.request, err)
|
messages.error(self.request, err)
|
||||||
@ -134,10 +137,11 @@ class DeleteEntitlementView(LoginRequiredMixin, View):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Deletes entitlement from API"""
|
"""Deletes entitlement from API"""
|
||||||
|
api = API(self.request.session.get('obp'))
|
||||||
try:
|
try:
|
||||||
urlpath = '/users/{}/entitlement/{}'.format(
|
urlpath = '/users/{}/entitlement/{}'.format(
|
||||||
kwargs['user_id'], kwargs['entitlement_id'])
|
kwargs['user_id'], kwargs['entitlement_id'])
|
||||||
api.delete(request, urlpath)
|
api.delete(urlpath)
|
||||||
msg = 'Entitlement with role {} has been deleted.'.format(
|
msg = 'Entitlement with role {} has been deleted.'.format(
|
||||||
request.POST.get('role_name', '<undefined>'))
|
request.POST.get('role_name', '<undefined>'))
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
|
|||||||
@ -2,4 +2,5 @@ Django==1.11.5
|
|||||||
oauthlib==2.0.0
|
oauthlib==2.0.0
|
||||||
requests==2.11.1
|
requests==2.11.1
|
||||||
requests-oauthlib==0.6.2
|
requests-oauthlib==0.6.2
|
||||||
|
PyJWT==1.5.3
|
||||||
gunicorn==19.6.0
|
gunicorn==19.6.0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user