Pipeline workflow and variables - python-social-auth

I have Facebook authentication working on my site, but I need the user to fill a profile form during his authentication. I have used an authentication pipeline to do so but whithout success. The pipeline is being called like it should, but the result is an error.
Let's say I need his mobile number - consider it does not come from Facebook.
Please consider:
models.py
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User)
mobile = models.IntegerField()
settings.py
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.mail.mail_validation',
'social.pipeline.user.create_user',
'social.pipeline.social_auth.associate_user',
'social.pipeline.social_auth.load_extra_data',
'social.pipeline.user.user_details',
'myapp.pipeline.fill_profile',
)
pipeline.py
from myapp.models import Profile
from social.pipeline.partial import partial
#partial
def fill_profile(strategy, details, user=None, is_new=False, *args, **kwargs):
try:
if user and user.profile:
return
except:
return redirect('myapp.views.profile')
myapp/views.py
from django.shortcuts import render, redirect
from myapp.models import Perfil
def profile(request):
if request.method == 'POST':
profile = Perfil(user=request.user,mobile=request.POST.get('mobile'))
profile.save()
backend = request.session['partial_pipeline']['backend']
redirect('social:complete', backend=)
return render(request,'profile.html')
The profile.html is just a form with an input text box named 'mobile' and a submit button.
Then I get this error:
Cannot assign "<SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x03C2FB10>>": "Profile.user" must be a "User" instance.
Why can't I access the User instance since the user in auth_user table is already there (I suppose)?
Please, what's wrong with this?

You can't access the user in request.user because it's not logged in yet, the user will be logged in social complete view after the pipeline executed. Usually partial pipeline views will save the form data into the session and then the pipeline will pick it and save it. Also you can set the user id in the session in your pipeline and then pick that value in your view. For example:
#partial
def fill_profile(strategy, user, *args, **kwargs):
...
strategy.session_set('user_id', user.id)
return redirect(...)

Related

Reuse log in state for all tests with pytest-playwright and Django

I'm trying to create a pytest fixture that will allow all my tests to reuse a single authenticated context. The goal is for the fixture to run once, yielding a context. Then the other tests can just reuse the authenticated context via the fixture. I also need this solution to run in CI.
The code below runs, but test_home does not retain a logged in state for some reason and redirects to the log in page.
conftest.py
import logging
import os
import pytest
from django.conf import settings
from user.models import User
from user.tests.factories import UserFactory
#pytest.fixture()
def user() -> User:
return UserFactory()
os.environ.setdefault("DJANGO_ALLOW_ASYNC_UNSAFE", "true")
settings.ALLOWED_HOSTS += ["127.0.0.1"]
#pytest.mark.django_db
#pytest.fixture()
def playwright_user(user):
password = "HappyAccident$1"
email = "bob.ross#happytrees.com"
user.email = email
user.set_password(password)
user.save()
logging.info("Saved test user")
#pytest.fixture()
def logged_in(
live_server,
playwright,
playwright_user,
) -> None:
"""Create context (i.e. general configuration) for Playwright"""
browser = playwright.chromium.launch(headless=False)
context = browser.new_context(
base_url="http://127.0.0.1:8080", storage_state="auth.json"
)
page = context.new_page()
page.goto("http://127.0.0.1:8080/")
page.get_by_placeholder("E-mail address login").fill("bob.ross#happytrees.com")
page.get_by_placeholder("Password password").fill("HappyAccident$1")
page.get_by_role("button", name="Sign In").click()
storage = context.storage_state(path="auth.json")
logging.info("Created authenticated context")
context.close()
#pytest.fixture(scope="session")
def context(playwright):
browser = playwright.chromium.launch(headless=False)
context = browser.new_context(
base_url="http://127.0.0.1:8080", storage_state="auth.json"
)
yield context
context.close()
test_integration.py (note: the home page below is log in protected.)
import logging
import pytest
from django.urls import reverse
from playwright.sync_api import Page, sync_playwright
pytestmark = pytest.mark.django_db
def test_login(live_server, logged_in):
logging.info("Logged In")
pass
def test_home(
context,
live_server,
page: Page,
) -> None:
"""Create a new Organization Parent Type"""
page.goto(reverse("home"))
# redirects to the log in page instead of taking me to the home page :(
I'm not sure why test_home goes to a log in screen rather than directly opening the home page. If things were working properly it should go directly to the home page. Any help would be really appreciated!

Graphene JWT authentication

I'm using graphene-jwt to authenticate user but my user has to multiple object return
here
def mutate(cls, root, info, **kwargs):
result = super().mutate(root, info, **kwargs)
...
How can I add additional query to 'filter' more like User.object.filter(user_type=3)? cause currently my code is like this
except (MultipleObjectsReturned, JSONWebTokenError) as e:
users = models.User.objects.get(email=kwargs.get("email"), user_type_id=3)
result = cls(
user=users,
errors=[Error()],
account_errors=[],
token=get_token(users))
user = result.user
I do get token even my password is wrong, it should be failed when the password is wrong.
Thanks

Best tools for creating a REST API with static data [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 3 years ago.
Improve this question
I need some Tips for building a REST API with about 35000 static (non-changing) JSON data.
It's my first time building a REST API seriously, so I need some design decision advice.
First, I was planning to use Flask to build the API since I am familiar with it and MongoDB to store the data. But I've heard that MongoDB is not a good choice for data that do not change.
What I would like to know are:
Which DB is suitable for this kind of data?
Is Flask a good choice if I am expecting many users using the API at the same time?
What are the brief steps for doing this? What I have in my mind right now is something like below:
Steps:
1) Upload my data to DB
2) Create a REST API that helps the user fetch the data
3) Upload the REST API to some server
4) Test with Postman to see if it works
Is my overall thought correct?
Any advice would be great. Thanks in advance.
If you are unsure about what DB to use I would just go with PostgreSQL. It's scalable so if you ever need to build on your dataset it will work just fine. In terms of performance it just depends on how many requests it gets, but I bet it could handle whatever you throw at it.
Regarding the API, if you are set with Flask then I recommend the package Flask-Restful. Outline your db using an ORM in a file called models.py. In a folder called resources, make files that serve as your API resources. Example would be blogposts.py, which would have a get request for all or a single post, post, put, and delete for single posts. Here is something I have for a really lightweight blog. Using peewee as an ORM and another package called Flask-HTTPAuth for authentication.
# blogposts.py
import json
from flask import jsonify, Blueprint, abort, make_response
from flask_restful import (Resource, Api, reqparse, inputs, fields,
url_for, marshal, marshal_with)
from auth import auth
import models
blogpost_fields = {
'id': fields.Integer,
'title': fields.String,
'content': fields.String,
'created': fields.DateTime
}
def blogpost_or_404(id):
try:
blogpost = models.BlogPost.get(models.BlogPost.id==id)
except models.BlogPost.DoesNotExist:
abort(404)
else:
return blogpost
class BlogPostList(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument(
'title',
required=True,
help='No title provided',
location=['form', 'json']
)
self.reqparse.add_argument(
'content',
required=False,
nullable=True,
location=['form', 'json'],
default=''
)
super().__init__()
def get(self):
blogpost = [marshal(blogpost, blogpost_fields)
for blogpost in models.BlogPost.select()]
return {'BlogPosts': blogpost}
#marshal_with(blogpost_fields)
#auth.login_required
def post(self):
args = self.reqparse.parse_args()
blogpost = models.BlogPost.create(**args)
return (blogpost, 201, {
'Location': url_for('resources.blogposts.blogpost', id=blogpost.id)
})
class BlogPost(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument(
'title',
required=False,
help='No title provided',
location=['form', 'json']
)
self.reqparse.add_argument(
'content',
required=False,
nullable=True,
location=['form', 'json'],
default=''
)
super().__init__()
#marshal_with(blogpost_fields)
def get(self, id):
return (blogpost_or_404(id))
#marshal_with(blogpost_fields)
#auth.login_required
def put(self, id):
args = self.reqparse.parse_args()
try:
blogpost = models.BlogPost.select().where(
models.BlogPost.id==id).get()
except models.BlogPost.DoesNotExist:
return make_response(json.dumps(
{'error': 'That blogpost does not exist or is not editable'}
), 403)
else:
query = blogpost.update(**args).where(models.BlogPost.id==id)
query.execute()
blogpost = (blogpost_or_404(id))
return (blogpost, 200, {
'Location': url_for('resources.blogposts.blogpost', id=id)
})
#auth.login_required
def delete(self, id):
try:
blogpost = models.BlogPost.select().where(
models.BlogPost.id==id).get()
except models.BlogPost.DoesNotExist:
return make_response(json.dumps(
{'error': 'That blogpost does not exist or is not editable'}
), 403)
else:
query = blogpost.delete().where(models.BlogPost.id==id)
query.execute()
return '', 204, {'Location': url_for('resources.blogposts.blogposts')}
blogposts_api = Blueprint('resources.blogposts', __name__)
api = Api(blogposts_api)
api.add_resource(
BlogPostList,
'/blogposts',
endpoint='blogposts'
)
api.add_resource(
BlogPost,
'/blogposts/<int:id>',
endpoint='blogpost'
)
Resource classes have methods with the http method name, this is what sets which methods are allowed. For instance, if I tried to delete to /blogposts without an ID, it would respond with method not allowed. Delete is only defined for a single post. Marshaling determines what information is in the response, you define it with blogpost_fields at the top. In the init of each class, we define the Request Parser which is what determines the information the API needs. In this example we only need a title and the post content. In a users resource you would add in things like email, username, password, verify password, admin status etc.
# models.py
import datetime
import jwt
from argon2 import PasswordHasher
from peewee import *
import config
DATABASE = PostgresqlDatabase('blogdb', user=config.DB['USER'], password=config.DB['PW'], host=config.DB['HOST'])
HASHER = PasswordHasher()
class User(Model):
username = CharField(unique=True)
email = CharField(unique=True)
password = CharField()
class Meta:
database = DATABASE
#classmethod
def create_user(cls, username, email, password, **kwargs):
email = email.lower()
try:
cls.select().where(
(cls.email==email)|(cls.username**username)
).get()
except cls.DoesNotExist:
user = cls(username=username, email=email)
user.password = user.set_password(password)
user.save()
return user
else:
raise Exception("User with that email or username already exists")
#staticmethod
def verify_auth_token(token):
try:
payload = jwt.decode(token, config.SECRET_KEY)
return payload['sub']
except jwt.ExpiredSignatureError:
return 'Signature expired. Please log in again.'
except jwt.InvalidTokenError:
return 'Invalid token. Please log in again.'
#staticmethod
def set_password(password):
return HASHER.hash(password)
def verify_password(self, password):
return HASHER.verify(self.password, password)
def generate_auth_token(self, id):
try:
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=0, seconds=5),
'iat': datetime.datetime.utcnow(),
'sub': id
}
return jwt.encode(
payload,
config.SECRET_KEY,
algorithm='HS256'
)
except Exception as e:
return e
class BlogPost(Model):
title = CharField(default='', unique=True)
content = TextField(default='')
created = DateTimeField(default=datetime.datetime.now)
class Meta:
database = DATABASE
def initialize():
DATABASE.connect()
DATABASE.create_tables([User, BlogPost], safe=True)
DATABASE.close()
# auth.py
from flask import g
from flask_httpauth import HTTPTokenAuth
import models
auth = HTTPTokenAuth(scheme='Bearer')
#auth.verify_token
def verify_token(token):
user = models.User.verify_auth_token(token)
if user is not None:
g.user = user
return True
return False
Models is pretty self explanatory if you've ever worked with an ORM like SQLAlchemy. I would recommend that package since your dataset is far far far larger than the one for this example. HTTPAuth allows you to decorate your API resource methods with a required authentication method. In my example, logging in will generate a JWT which needs to be sent with each request as a Bearer token.
Once all of that is set up you register your API blueprints in app.py
# app.py
app = Flask(__name__)
app.register_blueprint(users_api, url_prefix='/api/v1')
app.register_blueprint(blogposts_api, url_prefix='/api/v1')
app.register_blueprint(login_api)
That's it!

new request show up when I do nothing with request monitor

I run the codes from the book and successfully deploy it in my testing "deerdog.me" website as a portal.
There are 3 modules to work: registration, table create and requests monitor.
the 1st user registration module is good.
but when get in with the new registered id/pwd into left-top sign-in form. (under "home" hint, to click for popup)
you see the 2nd table_creating module seems good. create on table by each random number you input.
But the bug is here, here you should not touch with the 3rd module--request, but once you click the "dashboard" in the upper navigation bar.
In new page, weirdly you can see a new request be stealthy added. (theoretically ,the request row can only added by "add-request" function,but not now "get-request" function.
The biggest hint I found is that I only put the "add-request" under the route of #app.route("/newrequest/<tid>") ,but even the route #app.route("/account/createtable"... ,which only has "get-request" function ,can create request in mongodb's db.requests.().
Why does this happen, pls help me check and fix. Thank a lot.
Manipulation logic of 2nd module (creating table and display with the same "/account" route page ,
activate action == you input a random num(2,5 or 66,109) in blank and click "create button" ,then you will see the rows(include a shor url) in same "/account" page.
Manipulation logic of 3rd module (create requests then display in '/dashboard' route page.) should only be activated in such scenario (user copy the short URL in /account route page's table row, then ctrl+ p the short URl to a new browse address ,then under this new "#app.route("/newrequest/") ,a request in mongodb start to legally create" ,now user can jump the manipulation logic of 3rd module. directly create request record without initiating "#app.route("/newrequest/" ,Why does this happen?
#dbhelper.py as below
import pymongo
from bson.objectid import ObjectId
DATABASE = "waitercaller"
class DBHelper:
def __init__(self):
client = pymongo.MongoClient()
self.db = client[DATABASE]
def add_table(self, number, owner):
new_id = self.db.tables.insert({"number": number, "owner": owner})
return new_id
def update_table(self, _id, url):
self.db.tables.update({"_id": _id}, {"$set": {"url": url}})
def get_tables(self, owner_id):
return list(self.db.tables.find({"owner": owner_id}))
def get_table(self, table_id):
return self.db.tables.find_one({"_id": ObjectId(table_id)})
def delete_table(self, table_id):
self.db.tables.remove({"_id": ObjectId(table_id)})
def add_request(self, table_id, time):
table = self.get_table(table_id)
try:
self.db.requests.insert({"owner": table['owner'], "table_number": table[
'number'], "table_id": table_id, "time": time})
return True
except pymongo.errors.DuplicateKeyError:
return False
def get_requests(self, owner_id):
return list(self.db.requests.find({"owner": owner_id}))
def delete_request(self, request_id):
self.db.requests.remove({"_id": ObjectId(request_id)})
main APP code, called waitercaller.py
waitercaller.py
...
#app.route("/")
def home():
#return "Under construction"
#return render_template("home.html")
#registrationform = RegistrationForm()
return render_template("home.html", loginform=LoginForm(), registrationform=RegistrationForm())
#app.route("/dashboard")
#login_required
def dashboard():
now = datetime.datetime.now()
requests = DB.get_requests(current_user.get_id())
for req in requests:
deltaseconds = (now - req['time']).seconds
req['wait_minutes'] = "{}.{}".format((deltaseconds/60), str(deltaseconds % 60).zfill(2))
return render_template("dashboard.html", resolvesubmitform=ResolveForm(),requests=requests)
#app.route("/dashboard/resolve")
#login_required
def dashboard_resolve():
form = ResolveForm(request.form)
request_id = request.args.get("request_id")
#if form.validate():
DB.delete_request(request_id)
return redirect(url_for('dashboard'))
#return render_template("dashboard.html", resolvesubmitform=ResolveForm(),requests=DB.get_requests(request_id))
#app.route("/account")
#login_required
def account():
tables = DB.get_tables(current_user.get_id())
return render_template("account.html", createtableform=CreateTableForm(), tables=tables)
#app.route("/account/createtable", methods=["POST"])
#login_required
def account_createtable():
form = CreateTableForm(request.form)
if form.validate():
tableid = DB.add_table(form.tablenumber.data, current_user.get_id())
new_url = BH.shorten_url(config.base_url + "newrequest/" + str(tableid))
DB.update_table(tableid, new_url)
return redirect(url_for('account'))
return render_template("account.html", createtableform=form, tables=DB.get_tables(current_user.get_id()))
#app.route("/account/deletetable")
#login_required
def account_deletetable():
tableid = request.args.get("tableid")
DB.delete_table(tableid)
return redirect(url_for('account'))
#app.route("/newrequest/<tid>")
def new_request(tid):
DB.add_request(tid, datetime.datetime.now())
return "Your request has been logged and a waiter will be with you shortly"
if __name__ == '__main__':
app.run(port=5000, debug=True)

how to use facebook api on a group

i created an application to test facebook api using Python, then, in the application, i created its Group, but, the problem that this Group dont know who i'm; it shows me that am the Admin, but when i try to publish something using Tornado, i get the error
GraphAPIError: (#210) User not visible
and this is because it seems that it deletes the cookie, because when am using the group's profile, then i cant see the GraphAPI since it dont know who is authentificated!
here is the code:
class MainHandler(BaseHandler, tornado.auth.FacebookGraphMixin):
#tornado.web.authenticated
#tornado.web.asynchronous
def get(self):
self.facebook_request("/me/home", self.print_callback,access_token=self.current_user["access_token"])
a = self.current_user["access_token"]
self.graph = GraphAPI(a)
def print_callback(self, data):
self.graph.post_wall(self, "heloooooooo")
an sorry, because i dont get well the logic behind users Token, and Groups tokens? here i got a user Token? and because am the admin, i cant post!
EDIT: here are some snapshots i took from the application:
picture 1
picture 2
Update: i tried this:
def print_callback(self, me):
self.graph.post_wall(self, "helooooo", profile_id="267914489995838")
and got the error:
self.graph.post_wall(self, "helooooo", profile_id="267914489995838")
TypeError: post_wall() got multiple values for keyword argument 'profile_id'
and i used what is in the Tornado-Facebook-API
def post_wall(self, message, profile_id='me', body=None, **kwargs):
#XXX move to separate User class?
body = body or {}
body['message'] = message
self._make_request("{0}/feed".format(profile_id), method='POST',
body=body, **kwargs)
update2: here is the full code
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("user")
if not user_json: return None
return tornado.escape.json_decode(user_json)
class MainHandler(BaseHandler, tornado.auth.FacebookGraphMixin):
#tornado.web.authenticated
#tornado.web.asynchronous
def get(self):
self.facebook_request("/me/accounts", self._on_accounts,
access_token=self.current_user["access_token"])
self.a = self.current_user["access_token"]
self.graph = GraphAPI(self.a)
def _on_accounts(self, account):
if account is None:
# Session may have expired
print "on accounts failed"
return
for acc in account["data"]:
if acc["id"] == "267914489995838":
print acc["access_token"]
self.facebook_request("/PAGE_ID/feed",
post_args={"message": "Test"},
access_token=acc["access_token"],
callback=self.async_callback(self._on_page_post))
def _on_page_post(self, post):
if not post:
# Post failed
return
class AuthLoginHandler(BaseHandler, tornado.auth.FacebookGraphMixin):
#tornado.web.asynchronous
def get(self):
my_url = (self.request.protocol + "://" + self.request.host +
"/auth/login?next=" +
tornado.escape.url_escape(self.get_argument("next", "/")))
if self.get_argument("code", False):
self.get_authenticated_user(
redirect_uri=my_url,
client_id=self.settings["facebook_api_key"],
client_secret=self.settings["facebook_secret"],
code=self.get_argument("code"),
callback=self._on_auth)
return
self.authorize_redirect(redirect_uri=my_url,
client_id=self.settings["facebook_api_key"],
extra_params={"scope": "read_stream, publish_stream"})
def _on_auth(self, user):
if not user:
raise tornado.web.HTTPError(500, "Facebook auth failed")
self.set_secure_cookie("user", tornado.escape.json_encode(user))
self.redirect(self.get_argument("next", "/"))
class AuthLogoutHandler(BaseHandler, tornado.auth.FacebookGraphMixin):
def get(self):
self.clear_cookie("user")
self.redirect(self.get_argument("next", "/"))
class PostModule(tornado.web.UIModule):
def render(self, post):
return self.render_string("modules/post.html", post=post)
Some clarifications:
A #200 error is a permission error. In this case, you don't have permissions to post somewhere.
There are two main types of tokens: user tokens and page tokens
You can add a group to an application's roles http://developers.facebook.com/blog/post/531/
As far as I know, there is no way to add an application to a group
So I think based on the Facebook id given you either want to do two things
Post to an application's timeline
Post to a group via an application
Posting to an application's timeline
This requires the application page access token, which you get from /me/accounts using the manage_pages and publish_stream permissions
self.facebook_request("/me/accounts", self._on_accounts,
access_token=self.current_user["access_token"])
def _on_accounts(self, account):
if account is None:
# Session may have expired
print "on accounts failed"
return
for acc in account["data"]:
if acc["id"] == "PAGE_ID":
print acc["access_token"]
self.facebook_request("/PAGE_ID/feed",
post_args={"message": "Test"},
access_token=acc["access_token"],
callback=self.async_callback(self._on_page_post))
def _on_page_post(self, post):
if not post:
# Post failed
return
See http://developers.facebook.com/docs/reference/api/application/ for more info
A full example can be seen at https://gist.github.com/3867203 (Which doesn't handle duplicate posting)