I am writing REST api in flask for the first time,
so now I have something like this:
import uuid
import pytz
from datetime import datetime
from flask_restplus import Resource, Api, fields
from ..models import publicip_schema
from ..controller import (
jsonified,
get_user_ip,
add_new_userIp,
get_specificIp,
get_all_publicIp
)
from flask import request, jsonify
from src import app
from src import db
from src import models
api = Api(app, endpoint="/api", versio="0.0.1", title="Capture API", description="Capture API to get, modify or delete system services")
add_userIp = api.model("Ip", {"ip": fields.String("An IP address.")})
get_userIp = api.model("userIp", {
"ipid": fields.String("ID of an ip address."),
"urlmap" : fields.String("URL mapped to ip address.")
})
class CaptureApi(Resource):
# decorator = ["jwt_required()"]
# #jwt_required()
#api.expect(get_userIp)
def get(self, ipid=None, urlmap=None):
"""
this function handles request to provide all or specific ip
:return:
"""
# handle request to get detail of site with specific location id.
if ipid:
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
# handle request to get detail of site based on site abbreviation
if urlmap:
locate = get_user_ip({"urlmap": urlmap})
return jsonified(locate)
return jsonify(get_all_publicIp())
# #jwt_required()
#api.expect(add_userIp)
def post(self, username=None):
"""
Add a new location.
URI /location/add
:return: json response of newly added location
"""
data = request.get_json(force=True)
if not data:
return jsonify({"status": "no data passed"}), 200
if not data["ip"]:
return jsonify({"status" : "please pass the new ip you want to update"})
if get_user_ip({"ipaddress": data["ip"]}):
return jsonify({"status": "IP: {} is already registered.".format(data["ip"])})
_capIpObj = get_user_ip({"user_name": username})
if _capIpObj:
# update existing ip address
if "ip" in data:
if _capIpObj.ipaddress == data["ip"]:
return jsonify({"status": "nothing to update."}), 200
else:
_capIpObj.ipaddress = data["ip"]
else:
return jsonify({
"status" : "please pass the new ip you want to update"
})
db.session.commit()
return jsonified(_capIpObj)
else:
device = ""
service = ""
ipaddress = data["ip"]
if "port" in data:
port = data["port"]
else:
port = 80
if "device" in data:
device = data["device"]
if "service" in data:
service = data["service"]
date_modified = datetime.now(tz=pytz.timezone('UTC'))
urlmap = str(uuid.uuid4().get_hex().upper()[0:8])
new_public_ip = add_new_userIp(username, ipaddress, port, urlmap, device, service, date_modified)
return publicip_schema.jsonify(new_public_ip)
api.add_resource(
CaptureApi,
"/getallips", # GET
"/getip/id/<ipid>", # GET
"/getip/urlmap/<urlmap>", # GET
"/updateip/username/<username>" # POST
)
I have faced two problems
if I specify
get_userIp = api.model("userIp", {
"ipid": fields.String("ID of an ip address."),
"urlmap" : fields.String("URL mapped to ip address.")
})
and add #api.expect(get_userIp) on get method above. I am forced to pass optional parameters with any value (even to get list of all ip's i.e. from "/getallips"): see screenshot below.
but these option parameters are not required tog et all IP's, but I do need to use those parameters to get ip based on ipid, or urlmap using the get method.
looking at swagger documentation generated by flask_restplus.Api I am seeing
get and post for all the endpoints, whereas I have defined endpoint get and post only. So technically updateip/username/<username> should not be listing get
How do I fix this ?
Good question! You can fix both problems by defining separate Resource subclasses for each of your endpoints. Here is an example where I split the endpoints for "/getallips", "/getip/id/", and "/getip/urlmap/".
Ip = api.model("Ip", {"ip": fields.String("An IP address.")})
Urlmap = api.model("UrlMap", {"urlmap": fields.String("URL mapped to ip address.")})
#api.route("/getallips")
class IpList(Resource):
def get(self):
return jsonify(get_all_publicIp())
#api.route("/getip/id/<ipid>")
class IpById(Resource):
#api.expect(Ip)
def get(self, ipid):
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
#api.route("/getip/urlmap/<urlmap>")
class IpByUrlmap(Resource):
#api.expect(Urlmap)
def get(self, urlmap):
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
Notice that you solve your expect problem for free - because each endpoint now fully defines its interface, it's easy to attach a clear expectation to it. You also solve your "get and post defined for endpoints that shouldn't", you can decide for each endpoint whether it should have a get or post.
I'm using the api.route decorator instead of calling api.add_resource for each class because of personal preference. You can get the same behavior by calling api.add_resource(<resource subclass>, <endpoint>) for each new Resource subclass (e.g. api.add_resource(IpList, "/getallips"))
Related
I am trying to create ReCaptcha assessment using their REST API in my backend server.
From reading the documentation, I understand that the request body contains an instance of Assesment, but when I try to send a request, I receive the following error:
TypeError: Object of type Assessment is not JSON serializable
My code:
import requests
from google.cloud import recaptchaenterprise_v1
from google.cloud.recaptchaenterprise_v1 import Assessment
def create_assessment(project_id: str, recaptcha_site_key: str, token: str, recaptcha_action: str, apiKey:str):
# Create event object
event = recaptchaenterprise_v1.Event()
event.site_key = recaptcha_site_key
event.token = token
# Create assesment object
assessment = recaptchaenterprise_v1.Assessment()
assessment.event = event
# Set project name
project_name = "projects/"+project_id
response = requests.post(url="https://recaptchaenterprise.googleapis.com/v1/"+project_name+"/assessments?key="+apiKey, json=assessment)
return response
I tried to convert the assesment to JSON using dumps(), but I had no success.
I've also tried to write it as "skinny JSON" like so:
assessment = {
'event': {
'token': token,
'siteKey': recaptcha_site_key,
'expectedAction': 'LOGIN'
}
}
Even though I receive status code 200, it indicates that my request is MALFORMED, probably because I don't include some recaptchaenterprise_v1 objects that should be on the assesment.
Try to use the CreateAssessmentRequest to create the request instead, like so:
client = recaptchaenterprise_v1.RecaptchaEnterpriseServiceClient()
project_name = "projects/"+project_id
# Build the assessment request.
request = recaptchaenterprise_v1.CreateAssessmentRequest()
request.assessment = assessment
request.parent = project_name
response = client.create_assessment(request)
You can find a more complete code sample in GCP's documentation.
I have coded an authentication and I get an access token, but when I use it to grab an object I just end up getting 'NoneType' object is not callable.
Exception Location: googleads\adwords.py in GetService, line 365
Exception Type: Type Error
Exception Value: Internal Server Error: /oauth2callback
I get the same result whether calling for Customer or CampaignService. I don't understand what I am doing wrong.
I am following the code in Googleads.
def getAdwordsFlow():
FLOW.redirect_uri = 'http://localhost/oauth2callback'
# Generate URL for request to Google's OAuth 2.0 server.
authorization_url, state = FLOW.authorization_url(
access_type='offline',
include_granted_scopes='true')
return authorization_url
def getAdwordsTokens(request):
auth_code = request.GET.get('code')
FLOW.fetch_token(code=auth_code)
credentials = FLOW.credentials
oauth2_client = oauth2.GoogleAccessTokenClient(
FLOW.credentials.token, FLOW.credentials.expiry)
adwords_client = adwords.AdWordsClient(
"DEVELOPER-TOKEN", oauth2_client, "USER-AGENT", "CLIENT-CUSTOMER-ID")
customer = adwords_client.GetService('CustomerService').getCustomers()[0]
print('You are logged in as customer: %s' % customer['customerId'])
return HttpResponseRedirect("/")
url.py
urlpatterns = [
re_path(r'^oauth2callback', queries.getAdwordsTokens, name='auth_calledback'),] #How
view.py
def index(request):
return redirect(getAdwordsFlow())
Terminal output:
"GET /oauth2callback?state=XXXXXXXXX&code=4/XXXXXXXXXXX&scope=https://www.googleapis.com/auth/adwords+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email HTTP/1.1" 500 80213
Why is it 500?
I notice my access token has a different value when I call for it. So I am assuming it's working.
According to your question, your service call is -
GET /oauth2callback?state=XXXXXXXXX&code=4/XXXXXXXXXXX&scope=https://www.googleapis.com/auth/adwords+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email HTTP/1.1
This call is neither get access token call nor having access_token in the request and as per your reference code it will generate access_token from refresh_token.
def main(access_token, token_expiry, client_customer_id, developer_token,
user_agent):
oauth2_client = oauth2.GoogleAccessTokenClient(access_token, token_expiry)
adwords_client = adwords.AdWordsClient(
developer_token, oauth2_client, user_agent,
client_customer_id=client_customer_id)
customer = adwords_client.GetService('CustomerService').getCustomers()[0]
print 'You are logged in as customer: %s' % customer['customerId']
if __name__ == '__main__':
args = parser.parse_args()
# Retrieve a new access token for use in this example. In a production
# application, you may use a credential store to share access tokens for a
# given user across applications.
oauth2credentials = client.OAuth2Credentials(
None, args.client_id, args.client_secret, args.refresh_token,
datetime.datetime(1980, 1, 1, 12), GOOGLE_OAUTH2_ENDPOINT,
USER_AGENT)
oauth2credentials.refresh(httplib2.Http())
main(oauth2credentials.access_token, oauth2credentials.token_expiry,
args.client_customer_id, args.developer_token, USER_AGENT)
So to use your code, first generate refresh_token using this code and then use it in your given code.
I am developing a Gmail extracting app and using Gmail API to fetch mail from server. the problem lies in the fact that fetch time for mails is too large even though I used threading in back end framework. now I am going to implement one feature which will suggest user opting for bulk download that "once your download is ready, we will mail you" but for that i want to run download.py mentioned below in app tree in background and once the fetch is over it will get terminated.
And in the very bottom of the code i want to mail user that their download is ready but its not working though i have defined the mail server in settings.py .
download.py
import httplib2, base64
from stripogram import html2text
from oauth2client.django_orm import Storage
from apiclient.discovery import build
from oauth2client import client
from django.contrib.auth.models import User
from .models import CredentialsModel
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from gextracto import models
from gextracto.models import UserData
from django.core.mail import EmailMessage
from django.core import mail
connection = mail.get_connection()
class ListMails(APIView):
"""
Gets a list of a specified number mail ids for a particular label
Extracts the email in the form of plain/text
The API returns all the extracted mails
"""
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def extract_headers(self, message):
"""
Extract the headers for a single mail and returns it
{To, From, Subject}
"""
needed_fields = ('From', 'To', 'Subject')
return {i['name']:i['value'] for i in message['payload']['headers'] if i['name'] in needed_fields}
def get_message_body(self, message):
"""
Get the body of an email
Recursively look for the body for different mimetypes
Returns the body as text/plain
"""
if 'payload' in message:
return self.get_message_body(message['payload'])
elif 'parts' in message:
return self.get_message_body(message['parts'][0])
else:
data = base64.urlsafe_b64decode(message['body']['data'].encode('ASCII'))
markdown_data = html2text(data)#.decode('utf-8', "replace")
data = data.replace("\n", "<br/>")
# return {markdown, html}
return {'markdown':unicode( markdown_data,"ISO-8859-1"), 'html':unicode(data,"ISO-8859-1")} if markdown_data else {'html':unicode(data,"ISO-8859-1")}
def message_content_html(self, userId, message_id, service):
"""
Make queries to get the content for a mail given its message id
Returns all the content
"""
content = {'id':message_id}
# try
message = service.users().messages().get(userId=userId, id=message_id).execute()
mimetype = message['payload']['mimeType']
if mimetype == 'text/html':
return {}
#
else:
body = self.get_message_body(message)
if body == "":
body = "<empty message>"
headers = self.extract_headers(message)
content['body'] = body
content.update(headers)
return content
def collect_mails(self, user, messages, service):
"""
Collect the content for all the mails currently downloaded
"""
all_messages = []
try:
for message in messages:
content = self.message_content_html(user.username, message['id'], service)
if content:
all_messages.append(content)
return all_messages
# return empty list if no messages were downloaded
except KeyError:
return []
def get(self, request, format=None):
"""
Handles the GET request to get all the mails for a label
Paginages through the GAPI content if required
API returns all the messages
{To, From, Subject, body}
"""
user = request.user
storage = Storage(CredentialsModel, 'id', user, 'credential')
credentials = storage.get()
http_auth = credentials.authorize(httplib2.Http())
service = build('gmail', 'v1', http=http_auth)
user_Id = user.username
label_id = request.GET['label']
# try
# call Google API with a request to get a list of all the labels
response = service.users().messages().list(userId=user_Id, labelIds=label_id, maxResults=100).execute()
all_messages = self.collect_mails(user, response['messages'], service)
if not all_messages:
return Response([])
else:
if 'nextPageToken' in response:
page_token_flag = True
# request more more mails if the download limit has not yet been satisfied
while(page_token_flag):
response = service.users().messages().list(userId=user_Id, pageToken=response['nextPageToken'], maxResults=100).execute()
all_messages.append(self.collect_mails(user, response['messages'], service))
print(all_messages)
#for x in range(0,len(all_messages)):
#b=all_messages[10]
#instance= UserData(user_id=user ,label=label_id, sender = b['From'] , subject=b['Subject'] , body=b['body'])
#instance.save()
page_token_flag = 'nextPageToken' in response
##
for x in range(0,len(all_messages)):
b=all_messages[10]
instance= UserData(user_id=user ,label=label_id, sender = b['From'] , subject=b['Subject'] , body=b['body'])
instance.save()
print ("Hi i am here!!!")
email = EmailMessage('Your Download Ready!', 'http://127.0.0.1:8000/admin/gextracto/userdata/', to=[user], connection=connection)
email.send()
connection.close()
return Response(all_messages)
Please tell me the way to run it in background. if need any other info please do ask. Thanks
Don't know the exact requirements but I'll think about Celery to run background tasks. This approach allows to manage all post-script activities in native Django manner.
Also you can think about running the Django script using cron (as manage.py command) - but it can lead to some limitations.
What about sending emails failure - believe, you don't need to close connection after sending email. Usually I use send_mail()/send_mass_mail() functions - please, check their code to get an idea.
I have asked a few questions about this before, but still haven't solved my problem.
I am trying to allow Salesforce to remotely send commands to a Raspberry Pi via JSON (REST API). The Raspberry Pi controls the power of some RF Plugs via an RF Transmitter called a TellStick. This is all setup, and I can use Python to send these commands. All I need to do now is make the Pi accept JSON, then work out how to send the commands from Salesforce.
Someone kindly forked my repo on GitHub, and provided me with some code which should make it work. But unfortunately it still isn't working.
Here is the previous question: How to accept a JSON POST?
And here is the forked repo: https://github.com/bfagundez/RemotePiControl/blob/master/power.py
What do I need to do? I have sent test JSON messages n the Postman extension and in cURL but keep getting errors.
I just want to be able to send various variables, and let the script work the rest out.
I can currently post to a .py script I have with some URL variables, so /python.py?power=on&device=1&time=10&pass=whatever and it figures it out. Surely there's a simple way to send this in JSON?
Here is the power.py code:
# add flask here
from flask import Flask
app = Flask(__name__)
app.debug = True
# keep your code
import time
import cgi
from tellcore.telldus import TelldusCore
core = TelldusCore()
devices = core.devices()
# define a "power ON api endpoint"
#app.route("/API/v1.0/power-on/<deviceId>",methods=['POST'])
def powerOnDevice(deviceId):
payload = {}
#get the device by id somehow
device = devices[deviceId]
# get some extra parameters
# let's say how long to stay on
params = request.get_json()
try:
device.turn_on()
payload['success'] = True
return payload
except:
payload['success'] = False
# add an exception description here
return payload
# define a "power OFF api endpoint"
#app.route("/API/v1.0/power-off/<deviceId>",methods=['POST'])
def powerOffDevice(deviceId):
payload = {}
#get the device by id somehow
device = devices[deviceId]
try:
device.turn_off()
payload['success'] = True
return payload
except:
payload['success'] = False
# add an exception description here
return payload
app.run()
Your deviceID variable is a string, not an integer; it contains a '1' digit, but that's not yet an integer.
You can either convert it explicitly:
device = devices[int(deviceId)]
or tell Flask you wanted an integer parameter in the route:
#app.route("/API/v1.0/power-on/<int:deviceId>", methods=['POST'])
def powerOnDevice(deviceId):
where the int: part is a URL route converter.
Your views should return a response object, a string or a tuple instead of a dictionary (as you do now), see About Responses. If you wanted to return JSON, use the flask.json.jsonify() function:
# define a "power ON api endpoint"
#app.route("/API/v1.0/power-on/<int:deviceId>", methods=['POST'])
def powerOnDevice(deviceId):
device = devices[deviceId]
# get some extra parameters
# let's say how long to stay on
params = request.get_json()
try:
device.turn_on()
return jsonify(success=True)
except SomeSpecificException as exc:
return jsonify(success=False, exception=str(exc))
where I also altered the exception handler to handle a specific exception only; try to avoid Pokemon exception handling; do not try to catch them all!
To retrieve the Json Post values you must use request.json
if request.json and 'email' in request.json:
request.json['email']
I would like to enable the visitors of my website to login using their Google Accounts instead of having to sign up and create a new one.
A few things:
I am NOT using the Django authentication framework, instead, I do my own authentication and keep information about users in my own set of tables
consequently, various django-openid libs are not applicable since they all assume that the standard Django auth framework is used.
I tried to study the python-openid library + the google federated login API but I am lost. I get as close as to understand the instantiating the Consumer class but do not understand the session and store params required. I cannot fathom something that seems so easy can be so complicated. Is there really no step by step tutorial of how to do this in pure python or django?
I tried to look at the examples/consumer.py within the python-openid but it's 500lines of code again that I do not understand.
I also don't understand how verification of the user against google accounts is done on every request to my website. Google API only explains initial login steps. What happens on every request to my website where authentication must be verified against a google server?
I think your problem stems from a basic misunderstanding of how OpenID and/or OAuth work.
It looks like you just want authentication, so let's stick to OpenID for now. You are correct to look at existing libraries. python-openid is the one to use if you only need OpenID and not OAuth, and you are not using Django's built-in auth framework.
The full documentation for Federated Login with OpenID and OAuth is here: http://code.google.com/apis/accounts/docs/OpenID.html . In particular, look at the diagram under "Interaction sequence".
First, here is a very good working example from Facebook's Tornado web server's auth module:
https://github.com/facebook/tornado/blob/master/tornado/auth.py
(grep that for "GoogleHandler". I've used it with great success.)
This is independent of Django and Django auth, and should give you a good example of how to implement what you want. If that's still not enough, read on...
You said django-openid is irrelevant, but in fact it demonstrates an implementation of exactly what you want, but for Django's auth system instead of yours. Actually, you should look at the similar plugin, Django-SocialAuth, which implements OpenID + OAuth for a few different providers (Google, Facebook, Twitter, etc.). In particular, look at:
https://github.com/agiliq/Django-Socialauth/blob/master/socialauth/lib/oauthgoogle.py
and
https://github.com/agiliq/Django-Socialauth/tree/master/openid_consumer
and
https://github.com/agiliq/Django-Socialauth/tree/master/example_project
...for a full working example using django's auth framework, and can be adapted to your custom auth framework.
Best of luck. I encourage you to document whatever ends up working for you and build a step-by-step guide for others like you.
Federated Login for Google Account Users
django-openid
django-socialauth
I have managed to demistify the problem so here is the solution and I hope someone else can benefit from it:
1) Google Account verification is not done against the google accounts server on every request to your application. For example:
1.1 a user logs into your app using their gmail account
1.2 the user also navigates to gmail.com where they check their email
1.3 they log out of gmail
1.4 they remain logged into your application and can use it fully
This means you have to take care of session expiry on your end, Google account does not take care of it.
2) The core Python code I used is the following:
from openid.consumer.consumer import Consumer, \
SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
from openid.consumer.discover import DiscoveryFailure
from django.utils.encoding import smart_unicode
from myapp.common.util.openid import DjangoOpenIDStore
def google_signin(request):
""" This is the view where the Google account login icon on your site points to, e.g. http://www.yourdomain.com/google-signin """
consumer = Consumer(request.session, DjangoOpenIDStore())
# catch Google Apps domain that is referring, if any
_domain = None
if 'domain' in request.POST:
_domain = request.POST['domain']
elif 'domain' in request.GET:
_domain = request.GET['domain']
try:
# two different endpoints depending on whether the using is using Google Account or Google Apps Account
if _domain:
auth_request = consumer.begin('https://www.google.com/accounts/o8/site-xrds?hd=%s' % _domain)
else:
auth_request = consumer.begin('https://www.google.com/accounts/o8/id')
except DiscoveryFailure as e:
return CustomError(request, "Google Accounts Error", "Google's OpenID endpoint is not available.")
# add requests for additional account information required, in my case: email, first name & last name
auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'mode', 'fetch_request')
auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'required', 'email,firstname,lastname')
auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.email', 'http://schema.openid.net/contact/email')
auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.firstname', 'http://axschema.org/namePerson/first')
auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.lastname', 'http://axschema.org/namePerson/last')
return redirect(auth_request.redirectURL('http://www.yourdomain.com', 'http://www.yourdomain.com/google-signin-response')))
#transaction.commit_manually
def google_signin_response(request):
""" Callback from Google Account service with login the status. Your url could be http://www.yourdomain.com/google-signin-response """
transaction.rollback() # required due to Django's transaction inconsistency between calls
oidconsumer = Consumer(request.session, DjangoOpenIDStore())
# parse GET parameters submit them with the full url to consumer.complete
_params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
info = oidconsumer.complete(_params, request.build_absolute_uri().split('?')[0])
display_identifier = info.getDisplayIdentifier()
if info.status == FAILURE and display_identifier:
return CustomError(request, _("Google Login Error"), _("Verification of %(user)s failed: %(error_message)s") % {'user' : display_identifier, 'error_message' : info.message})
elif info.status == SUCCESS:
try:
_email = info.message.args[('http://openid.net/srv/ax/1.0', 'value.email')]
_first_name = info.message.args[('http://openid.net/srv/ax/1.0', 'value.firstname')]
_last_name = info.message.args[('http://openid.net/srv/ax/1.0', 'value.lastname')]
try:
_user = User.objects.get(email__iexact=_email)
except ObjectDoesNotExist:
# create a new account if one does not exist with the authorized email yet and log that user in
_new_user = _new_account(_email, _first_name + ' ' + _last_name, _first_name, _last_name, p_account_status=1)
_login(request, _new_user, info.message.args[('http://specs.openid.net/auth/2.0', 'response_nonce')])
transaction.commit()
return redirect('home')
else:
# login existing user
_login(request, _user, info.message.args[('http://specs.openid.net/auth/2.0', 'response_nonce')])
transaction.commit()
return redirect('home')
except Exception as e:
transaction.rollback()
system_log_entry(e, request=request)
return CustomError(request, _("Login Unsuccessful"), "%s" % e)
elif info.status == CANCEL:
return CustomError(request, _("Google Login Error"), _('Google account verification cancelled.'))
elif info.status == SETUP_NEEDED:
if info.setup_url:
return CustomError(request, _("Google Login Setup Needed"), _('Setup needed') % { 'url' : info.setup_url })
else:
# This means auth didn't succeed, but you're welcome to try
# non-immediate mode.
return CustomError(request, _("Google Login Setup Needed"), _('Setup needed'))
else:
# Either we don't understand the code or there is no
# openid_url included with the error. Give a generic
# failure message. The library should supply debug
# information in a log.
return CustomError(request, _("Google Login Error"), _('Google account verification failed for an unknown reason. Please try to create a manual account on Acquee.'))
def get_url_host(request):
if request.is_secure():
protocol = 'https'
else:
protocol = 'http'
host = escape(get_host(request))
return '%s://%s' % (protocol, host)
3) an additional lib I created and imported above (myapp.common.util.openid) is a merge of a few existing Django openID libs so kudos to those guys:
from django.db import models
from django.conf import settings
from django.utils.hashcompat import md5_constructor
from openid.store.interface import OpenIDStore
import openid.store
from openid.association import Association as OIDAssociation
import time, base64
from myapp.common.db.accounts.models import Association, Nonce
class DjangoOpenIDStore(OpenIDStore):
"""
The Python openid library needs an OpenIDStore subclass to persist data
related to OpenID authentications. This one uses our Django models.
"""
def storeAssociation(self, server_url, association):
assoc = Association(
server_url = server_url,
handle = association.handle,
secret = base64.encodestring(association.secret),
issued = association.issued,
lifetime = association.issued,
assoc_type = association.assoc_type
)
assoc.save()
def getAssociation(self, server_url, handle=None):
assocs = []
if handle is not None:
assocs = Association.objects.filter(
server_url = server_url, handle = handle
)
else:
assocs = Association.objects.filter(
server_url = server_url
)
if not assocs:
return None
associations = []
for assoc in assocs:
association = OIDAssociation(
assoc.handle, base64.decodestring(assoc.secret), assoc.issued,
assoc.lifetime, assoc.assoc_type
)
if association.getExpiresIn() == 0:
self.removeAssociation(server_url, assoc.handle)
else:
associations.append((association.issued, association))
if not associations:
return None
return associations[-1][1]
def removeAssociation(self, server_url, handle):
assocs = list(Association.objects.filter(
server_url = server_url, handle = handle
))
assocs_exist = len(assocs) > 0
for assoc in assocs:
assoc.delete()
return assocs_exist
def useNonce(self, server_url, timestamp, salt):
# Has nonce expired?
if abs(timestamp - time.time()) > openid.store.nonce.SKEW:
return False
try:
nonce = Nonce.objects.get(
server_url__exact = server_url,
timestamp__exact = timestamp,
salt__exact = salt
)
except Nonce.DoesNotExist:
nonce = Nonce.objects.create(
server_url = server_url,
timestamp = timestamp,
salt = salt
)
return True
nonce.delete()
return False
def cleanupNonce(self):
Nonce.objects.filter(
timestamp__lt = (int(time.time()) - nonce.SKEW)
).delete()
def cleaupAssociations(self):
Association.objects.extra(
where=['issued + lifetimeint < (%s)' % time.time()]
).delete()
def getAuthKey(self):
# Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY
return md5_constructor.new(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN]
def isDumb(self):
return False
4) and the model that is required in order to hold google account session identifiers and verified endpoints:
class Nonce(models.Model):
""" Required for OpenID functionality """
server_url = models.CharField(max_length=255)
timestamp = models.IntegerField()
salt = models.CharField(max_length=40)
def __unicode__(self):
return u"Nonce: %s for %s" % (self.salt, self.server_url)
class Association(models.Model):
""" Required for OpenID functionality """
server_url = models.TextField(max_length=2047)
handle = models.CharField(max_length=255)
secret = models.TextField(max_length=255) # Stored base64 encoded
issued = models.IntegerField()
lifetime = models.IntegerField()
assoc_type = models.TextField(max_length=64)
def __unicode__(self):
return u"Association: %s, %s" % (self.server_url, self.handle)
Good luck!
Rok