There is a Flask-Appbuilder app with a custom SecurityManager that looks up the user token that it gets from a browser. We get the client credentials on the start of the app. And it works fine till the moment the credentials rotate.
Is there an extension point where I can implement requesting customer_id and customer_secret from an external resource?
SecurityManager implementation:
class MySecurityManager(SecurityManager):
TOKENINFO_URL = "..."
USERINFO_URL = ".../{}"
def __init__(self, appbuilder):
super(MySecurityManager, self).__init__(appbuilder)
def get_oauth_user_info(self, provider, resp=None):
"""
We authenticate users against Our OAuth provider
"""
if provider == 'MyProvider':
tokeninfo = self.appbuilder.sm.oauth_remotes[provider].get(self.TOKENINFO_URL)
uid = tokeninfo.data.get('uid')
user = self.appbuilder.sm.oauth_remotes[provider].get(self.USERINFO_URL.format(uid))
log.debug("Token info: {0}".format(tokeninfo.data))
log.debug("User info: {0}".format(user.data))
return {'username': tokeninfo.data.get('uid', ''),
'email': user.data.get('email', ''),
'first_name': user.data.get('name', '').split(" ")[0],
'last_name': user.data.get('name', '').split(" ")[-1]}
else:
return super(MySecurityManager, self).get_oauth_user_info(provider, resp=None)
config.py:
OAUTH_PROVIDERS = [
{
'name': 'MyProvider',
'icon': ...,
'token_key': ...,
'remote_app': {
'base_url': ...,
'consumer_key': SUPERSET_OAUTH_CONSUMER_KEY,
'consumer_secret': SUPERSET_OAUTH_CONSUMER_SECRET,
'request_token_params': {
'scope': ...,
},
'request_token_url': ...,
'access_token_url': ...,
'authorize_url': ...,
}
}
]
I solved it overriding the get oauth_providers form https://github.com/dpgaspar/Flask-AppBuilder/blob/master/flask_appbuilder/security/manager.py#L306.
Here an example:
#property
def oauth_providers(self):
providers = self.appbuilder.get_app.config['OAUTH_PROVIDERS']
for provider in providers:
if provider['name'] == 'XXXX':
# rotate logic here
provider['remote_app']['consumer_key'] = xxxxx
provider['remote_app']['consumer_secret'] = xxxx
return providers
Related
I am using simplejwt to get an access and refresh tokens. In a view I need to create a dict, where the both will be stored as well as access token claims and another additional data. Everything works but by some reason when the refrsh token is added to dict, it returns its decoded value, but not the token.
my views.py
#csrf_exempt
##api_view(('GET',))
def check_token(request):
token_refresh = RefreshToken.for_user(request.user)
print('REFRESH', token_refresh)
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token, 'refresh_token': token_refresh}
try:
valid_data = TokenBackend(algorithm='HS256').decode(token, verify=False)
data['uui'] = valid_data['user_id']
data['validUntil'] = valid_data['exp']
data['clientId'] = 'default'
print(data)
return JsonResponse(data)
except ValidationError as v:
print("Validation error", v)
print('REFRESH', token_refresh) returns the token:
'REFRESH eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI...'
but data object returns:
{'token': 'eyJ0eXAiOiJKV1QiLCJhbGci...', 'refresh_token': {'token_type': 'refresh', 'exp': 1628664751, 'jti': '38f0e3a4d7bb452681834a6b149aa496', 'user_id': 'None'}, 'uui': 1, 'validUntil': 1628059131, 'clientId': 'default'}
my ideal result:
{'token': 'eyJ0eXAiOiJKV1QiLCJhbGci...', 'refresh_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI...', 'uui': 1, 'validUntil': 1628059131, 'clientId': 'default'}
if you want to create tokens manually for user in djangorestframework-simplejwt you can do this:
from rest_framework_simplejwt.tokens import RefreshToken
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
# Add additional fields here
}
now you can use this function in your views.
I'm getting an error:
File "/home/ofw/playlister/app.py", line 7
playlists = [
^
SyntaxError: invalid syntax
This is my app.py code:
from flask import Flask, render_template
app = Flask(__name__)
#app.route('/')
playlists = [
{ 'title': 'Cat Videos', 'description': 'Cats acting weird' },
{ 'title': '80\'s Music', 'description': 'Don\'t stop believing!' }
]
#app.route('/)
def playlists_index():
"""Show all playlists."""
return render_template('playlists_index.html', playlists=playlists)
I'm guessing it's related to the playlist array. Any suggestions? In advance, thanks.
You are using a decorator on line 5 but there is nothing to decorate below. There must be a function/method.
The one on line 11 is correct.
the positioning of your playlist variable is wrong, because it's directly under a decorator "#app.route" What SHOULD come after a decorator is a function. try editing the code this way:
from flask import Flask, render_template
app = Flask(__name__)
#app.route('/)
def playlists_index():
"""Show all playlists."""
playlists = [
{ 'title': 'Cat Videos', 'description': 'Cats acting weird' },
{ 'title': '80\'s Music', 'description': 'Don\'t stop believing!' }
]
return render_template('playlists_index.html', playlists=playlists)
or creating the playlist variable globally, this way:
playlists = [
{ 'title': 'Cat Videos', 'description': 'Cats acting weird' },
{ 'title': '80\'s Music', 'description': 'Don\'t stop believing!' }
]
#app.route('/')
def playlists_index():
"""Show all playlists."""
return render_template('playlists_index.html', playlists=playlists)
don't forget to correct your route string on line 11 to:
#app.route('/')
I want to use this route here (below) and render my react file. Using a python route.... Could someone give me some direction on how I can accomplish this?
#Auth.route('/login', methods=['GET'])
def login():
#data = {'username':'bob', 'password':'peepee123'}
#session['token'] = 'jsdkfkj934ujeklfjdlndsflds'
auth = request.authorization
if auth and auth.password == 'password':
token = jwt.encode({'user': auth.username}, app.config['SECRET_KEY'])
return jsonfiy({'token': token.decode('UTF-8')})
return make_response('Could Not verify!', 401, {'WWW-Authenticate': 'Basic realm = "Login Required"'})
If you want to render react in server site in python you can use python-react-v8, but you need to have the same react tree in server and client or it wont work, checkout hydrate in docs. For that you need to have working react app.
Example of usage:
import react
# setup react
react.set_up() # Initialize V8 machinery
react.utils.load_libs(['./bundle.js'])
#Auth.route('/login', methods=['GET'])
def login():
#data = {'username':'bob', 'password':'peepee123'}
#session['token'] = 'jsdkfkj934ujeklfjdlndsflds'
auth = request.authorization
if auth and auth.password == 'password':
token = jwt.encode({'user': auth.username}, app.config['SECRET_KEY'])
data = {'token': token.decode('UTF-8')};
react_ = react.React({
'url': request.get_full_url(),
'data': data
})
context = {
'content': react_.render(),
'data': react_.to_json(data)}
return render('index.html', context);
data = {'token': null, 'reason': "Login Required"}
react_ = react.React({
'url': request.get_full_url(),
'data': data
})
context = {
'content': react_.render(),
'data': react_.to_json(data)
}
return render('index.html', context);
I am having a problem understanding how mock works and how to write unittests with mock objects. I wanted to mock an external api call every time when my model calls save() method.
My code:
models.py
from . import utils
class Book(Titleable, Isactiveable, Timestampable, IsVoidable, models.Model):
title
orig_author
orig_title
isbn
def save(self, *args, **kwargs):
if self.isbn:
google_data = utils.get_original_title_and_name(self.isbn)
if google_data:
self.original_author = google_data['author']
self.original_title = google_data['title']
super().save(*args, **kwargs)
utils.py
def get_original_title_and_name(isbn, **kawargs):
isbn_search_string = 'isbn:{}'.format(isbn)
payload = {
'key': GOOGLE_API_KEY,
'q': isbn_search_string,
'printType': 'books',
}
r = requests.get(GOOGLE_API_URL, params=payload)
response = r.json()
if 'items' in response.keys():
title = response['items'][THE_FIRST_INDEX]['volumeInfo']['title']
author = response['items'][THE_FIRST_INDEX]['volumeInfo']['authors'][THE_FIRST_INDEX]
return {
'title': title,
'author': author
}
else:
return None
I began read docs and write test:
test.py:
from unittest import mock
from django.test import TestCase
from rest_framework import status
from .constants import THE_FIRST_INDEX, GOOGLE_API_URL, GOOGLE_API_KEY
class BookModelTestCase(TestCase):
#mock.patch('requests.get')
def test_get_original_title_and_name_from_google_api(self, mock_get):
# Define new Mock object
mock_response = mock.Mock()
# Define response data from Google API
expected_dict = {
'kind': 'books#volumes',
'totalItems': 1,
'items': [
{
'kind': 'books#volume',
'id': 'IHxXBAAAQBAJ',
'etag': 'B3N9X8vAMWg',
'selfLink': 'https://www.googleapis.com/books/v1/volumes/IHxXBAAAQBAJ',
'volumeInfo': {
'title': "Alice's Adventures in Wonderland",
'authors': [
'Lewis Carroll'
]
}
}
]
}
# Define response data for my Mock object
mock_response.json.return_value = expected_dict
mock_response.status_code = 200
# Define response for the fake API
mock_get.return_value = mock_response
The first of all, I can't write target for the #mock.patch correct. If a define target as utuls.get_original_title_and_name.requests.get, I get ModuleNotFoundError. Also I can't understand how to make fake-call to external API and verify recieved data (whether necessarly its, if I've already define mock_response.json.return_value = expected_dict?) and verify that my save() method work well?
How do I write test for this cases? Could anyone explain me this case?
You should mock the direct collaborators of the code under test. For Book that would be utils. For utils that would be requests.
So for the BookModelTestCase:
class BookModelTestCase(TestCase):
#mock.patch('app.models.utils')
def test_save_book_calls_google_api(self, mock_utils):
mock_utils.get_original_title_and_name.return_value = {
'title': 'Google title',
'author': 'Google author'
}
book = Book(
title='Some title',
isbn='12345'
)
book.save()
self.assertEqual(book.title, 'Google title')
self.assertEqual(book.author, 'Google author')
mock_utils.get_original_title_and_name.assert_called_once_with('12345')
And then you can create a separate test case to test get_original_title_and_name:
class GetOriginalTitleAndNameTestCase(TestCase):
#mock.patch('app.utils.requests.get')
def test_get_original_title_and_name_from_google_api(self, mock_get):
mock_response = mock.Mock()
# Define response data from Google API
expected_dict = {
'kind': 'books#volumes',
'totalItems': 1,
'items': [
{
'kind': 'books#volume',
'id': 'IHxXBAAAQBAJ',
'etag': 'B3N9X8vAMWg',
'selfLink': 'https://www.googleapis.com/books/v1/volumes/IHxXBAAAQBAJ',
'volumeInfo': {
'title': "Alice's Adventures in Wonderland",
'authors': [
'Lewis Carroll'
]
}
}
]
}
# Define response data for my Mock object
mock_response.json.return_value = expected_dict
mock_response.status_code = 200
# Define response for the fake API
mock_get.return_value = mock_response
# Call the function
result = get_original_title_and_name(12345)
self.assertEqual(result, {
'title': "Alice's Adventures in Wonderland",
'author': 'Lewis Carroll'
})
mock_get.assert_called_once_with(GOOGLE_API_URL, params={
'key': GOOGLE_API_KEY,
'q': 'isbn:12345',
'printType': 'books',
})
I want to set value in session using Pycket session manager. Look at the code:
session = SessionManager(self)
session['key'] = 'OMG'
After that in another handler I've used the following code:
session = SessionManager(self)
self.write(str(session['key']))
It writes None! What should I do?
Note: redis is working fine on my project and this is my tornado settings:
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(
url_patterns,debug=True,
cookie_secret="61oETz3455545gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
xsrf_cookies= False,
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path= os.path.join(os.path.dirname(__file__), "static"),
**{
'pycket': {
'engine': 'redis',
'storage': {
'db_sessions': 10,
'db_notifications': 11,
'max_connections': 2 ** 31,
},
'cookies': {
'expires_days': 120,
# 'domain' : SharedConnections.SiteNameUrl[SharedConnections.SiteNameUrl.index(".")+1,-1],
'domain' : 'domain.com',
},
},
}
)
use this way
session.set('key', 'OMG')
I suggest for use pycket follow this way
import tornado.web
from pycket.session import SessionMixin
from pycket.notification import NotificationMixin
class BaseHandler(tornado.web.RequestHandler, SessionMixin, NotificationMixin):
def __init__(self, application, request, **kwargs):
super(BaseHandler, self).__init__(application, request, **kwargs)
class IndexHandler(BaseHandler):
def get(self, *args, **kwargs):
self.session.set('key', 'value')
p = self.session.get('key')
self.render('index.html')