Parcourir la source

Commonified OAuth v2 based login using Facebook, Google and GitHub

version-14
Anand Doshi il y a 11 ans
Parent
révision
9f61571c0c
10 fichiers modifiés avec 212 ajouts et 145 suppressions
  1. +2
    -5
      frappe/core/doctype/profile/profile.py
  2. +17
    -1
      frappe/core/doctype/profile/profile.txt
  3. +30
    -0
      frappe/data/sample_site_config.json
  4. +0
    -62
      frappe/templates/includes/login.js
  5. +18
    -12
      frappe/templates/pages/login.html
  6. +141
    -60
      frappe/templates/pages/login.py
  7. +1
    -1
      frappe/website/doctype/blog_category/blog_category.py
  8. +2
    -1
      frappe/website/render.py
  9. +0
    -2
      frappe/website/sitemap.py
  10. +1
    -1
      requirements.txt

+ 2
- 5
frappe/core/doctype/profile/profile.py Voir le fichier

@@ -110,11 +110,8 @@ class DocType:
def update_gravatar(self):
import md5
if not self.doc.user_image:
if self.doc.fb_username:
self.doc.user_image = "https://graph.facebook.com/" + self.doc.fb_username + "/picture"
else:
self.doc.user_image = "https://secure.gravatar.com/avatar/" + md5.md5(self.doc.name).hexdigest() \
+ "?d=retro"
self.doc.user_image = "https://secure.gravatar.com/avatar/" + md5.md5(self.doc.name).hexdigest() \
+ "?d=retro"
def reset_password(self):
from frappe.utils import random_string, get_url


+ 17
- 1
frappe/core/doctype/profile/profile.txt Voir le fichier

@@ -2,7 +2,7 @@
{
"creation": "2013-03-07 11:54:44",
"docstatus": 0,
"modified": "2014-02-26 17:40:31",
"modified": "2014-02-27 18:01:32",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -484,6 +484,22 @@
"permlevel": 0,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "github_userid",
"fieldtype": "Data",
"label": "Github User ID",
"permlevel": 0,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "github_username",
"fieldtype": "Data",
"label": "Github Username",
"permlevel": 0,
"read_only": 1
},
{
"create": 1,
"doctype": "DocPerm",


+ 30
- 0
frappe/data/sample_site_config.json Voir le fichier

@@ -0,0 +1,30 @@
{
"db_name": "testdb",
"db_password": "password",
"mute_emails": true,
"developer_mode": 1,
"auto_cache_clear": true,
"disable_website_cache": true,
"max_file_size": 1000000,
"mail_server": "localhost",
"mail_login": null,
"mail_password": null,
"mail_port": 25,
"use_ssl": 0,
"auto_email_id": "hello@example.com",
"google_sign_in": {
"client_id": "google_client_id",
"client_secret": "google_client_secret"
},
"github_sign_in": {
"client_id": "github_client_id",
"client_secret": "github_client_secret"
},
"facebook_sign_in": {
"client_id": "facebook_client_id",
"client_secret": "facebook_client_secret"
}
}

+ 0
- 62
frappe/templates/includes/login.js Voir le fichier

@@ -112,65 +112,3 @@ frappe.ready(function() {
login.login();
$(document).trigger('login_rendered');
});

{% if fb_app_id is defined -%}
// facebook login

$(function() {
$login = $(".btn-facebook").prop("disabled", true);
$.getScript('//connect.facebook.net/en_UK/all.js', function() {
$login.prop("disabled", false);
FB.init({
appId: '{{ fb_app_id }}',
});
$login.click(function() {
$login.prop("disabled", true).html("Logging In...");
login.via_facebook();
});
});
});

login.via_facebook = function() {
// not logged in to facebook either
FB.login(function(response) {
if (response.authResponse) {
// yes logged in via facebook
console.log('Welcome! Fetching your information.... ');
var fb_access_token = response.authResponse.accessToken;

// get user graph
FB.api('/me', function(response) {
response.fb_access_token = fb_access_token || "[none]";
$.ajax({
url:"/",
type: "POST",
data: {
cmd:"frappe.templates.pages.login.login_via_facebook",
data: JSON.stringify(response)
},
statusCode: login.login_handlers
})
});
} else {
frappe.msgprint("You have denied access to this application via Facebook. \
Please change your privacy settings in Facebook and try again. \
If you do not want to use Facebook login, <a href='/login'>sign-up</a> here");
}
},{scope:"email"});
}

frappe.ready(function() {
var user_id = frappe.get_cookie("user_id");
var sid = frappe.get_cookie("sid");

// logged in?
if(!sid || sid==="Guest") {
// fallback on facebook login -- no login again
$(".btn-facebook").removeAttr("disabled");
} else {
// get private stuff (if access)
// app.setup_user({"user": user_id});
}
});

{%- endif %}

+ 18
- 12
frappe/templates/pages/login.html Voir le fichier

@@ -13,19 +13,25 @@
</label> -->
<br>
<button class="btn btn-lg btn-primary btn-block btn-login" type="submit">{{ _("Sign in") }}</button>
{%- if fb_app_id is defined -%}
<div id="fb-root"></div>
<p class="text-center">
<p class="text-muted text-center" style="margin: 10px;">or</p>
<button type="button" class="btn btn-lg btn-primary btn-block btn-facebook">
{{ _("Login via Facebook") }}</button></p>
{%- if third_party_sign_in -%}
<p class="text-muted text-center" style="margin: 10px;">or login via</p>
<p class="text-muted text-center" style="margin: 10px;">
{%- if facebook_sign_in is defined %}
<a href="{{ facebook_sign_in }}" class="btn btn-lg btn-block btn-primary btn-facebook">
<i class="icon-facebook-sign"></i> {{ _("Facebook") }}</a>
{%- endif -%}
{%- if google_sign_in is defined %}
<a href="{{ google_sign_in }}" class="btn btn-lg btn-block btn-primary btn-google">
<i class="icon-google-plus-sign"></i> {{ _("Google") }}</a>
{%- endif -%}
{%- if github_sign_in is defined %}
<a href="{{ github_sign_in }}" class="btn btn-lg btn-block btn-primary btn-github">
<i class="icon-github-sign"></i> {{ _("GitHub") }}</a>
{%- endif -%}
</p>
{%- endif -%}
{%- if google_sign_in is defined %}
<p class="text-center">
<p class="text-muted text-center" style="margin: 10px;">or</p>
<a href="{{ google_sign_in }}" type="button" class="btn btn-lg btn-primary btn-block btn-google">
{{ _("Login via Google") }}</a></p>
{%- endif -%}
<p class="text-center">
<br><a href="#signup">{{ _("Not a user yet? Sign up") }}</a>
</p>


+ 141
- 60
frappe/templates/pages/login.py Voir le fichier

@@ -6,114 +6,195 @@ import frappe, os
import httplib2
import json
from werkzeug.utils import redirect
import frappe.utils

no_cache = True

def get_context(context):
# get settings from site config
context["title"] = "Login"
if frappe.conf.get("fb_app_id"):
context.update({ "fb_app_id": frappe.conf.fb_app_id })
for provider in ("google", "github", "facebook"):
if get_oauth_keys(provider):
context["{provider}_sign_in".format(provider=provider)] = get_oauth2_authorize_url(provider)
context["third_party_sign_in"] = True
return context
oauth2_providers = {
"google": {
"flow_params": {
"name": "google",
"authorize_url": "https://accounts.google.com/o/oauth2/auth",
"access_token_url": "https://accounts.google.com/o/oauth2/token",
"base_url": "https://www.googleapis.com",
},
if os.path.exists(frappe.get_site_path("google_config.json")):
context.update({ "google_sign_in": get_google_auth_url() })
"redirect_uri": "/api/method/frappe.templates.pages.login.login_via_google",
return context
"auth_url_data": {
"scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
"response_type": "code"
},
# relative to base_url
"api_endpoint": "oauth2/v2/userinfo"
},
"github": {
"flow_params": {
"name": "github",
"authorize_url": "https://github.com/login/oauth/authorize",
"access_token_url": "https://github.com/login/oauth/access_token",
"base_url": "https://api.github.com/"
},
"redirect_uri": "/api/method/frappe.templates.pages.login.login_via_github",
# relative to base_url
"api_endpoint": "user"
},
"facebook": {
"flow_params": {
"name": "facebook",
"authorize_url": "https://www.facebook.com/dialog/oauth",
"access_token_url": "https://graph.facebook.com/oauth/access_token",
"base_url": "https://graph.facebook.com"
},
"redirect_uri": "/api/method/frappe.templates.pages.login.login_via_facebook",
"auth_url_data": {
"display": "page",
"response_type": "code",
"scope": "email,user_birthday"
},
# relative to base_url
"api_endpoint": "me"
}
}

def get_google_auth_url():
flow = get_google_auth_flow()
return flow.step1_get_authorize_url()
def get_oauth_keys(provider):
# get client_id and client_secret from conf
return frappe.conf.get("{provider}_sign_in".format(provider=provider))

def get_google_auth_flow():
from oauth2client.client import flow_from_clientsecrets
google_config_path = frappe.get_site_path("google_config.json")
google_config = frappe.get_file_json(google_config_path)
flow = flow_from_clientsecrets(google_config_path,
scope=['https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email'],
redirect_uri=google_config.get("web").get("redirect_uris")[0])
def get_oauth2_authorize_url(provider):
flow = get_oauth2_flow(provider)
# relative to absolute url
data = { "redirect_uri": get_redirect_uri(provider) }

return flow
# additional data if any
data.update(oauth2_providers[provider].get("auth_url_data", {}))
return flow.get_authorize_url(**data)
def get_oauth2_flow(provider):
from rauth import OAuth2Service
# get client_id and client_secret
params = get_oauth_keys(provider)
# additional params for getting the flow
params.update(oauth2_providers[provider]["flow_params"])
# and we have setup the communication lines
return OAuth2Service(**params)
def get_redirect_uri(provider):
redirect_uri = oauth2_providers[provider]["redirect_uri"]
return frappe.utils.get_url(redirect_uri)
@frappe.whitelist(allow_guest=True)
def login_via_google(code):
flow = get_google_auth_flow()
credentials = flow.step2_exchange(code)
login_via_oauth2("google", code, decoder=json.loads)
@frappe.whitelist(allow_guest=True)
def login_via_github(code):
login_via_oauth2("github", code)

http = httplib2.Http()
http = credentials.authorize(http)
@frappe.whitelist(allow_guest=True)
def login_via_facebook(code):
login_via_oauth2("facebook", code)
resp, content = http.request('https://www.googleapis.com/oauth2/v2/userinfo', 'GET')
info = json.loads(content)
def login_via_oauth2(provider, code, decoder=None):
flow = get_oauth2_flow(provider)
if not info.get("verified_email"):
frappe.throw("You need to verify your email with Google before you can proceed.")
args = {
"data": {
"code": code,
"redirect_uri": get_redirect_uri(provider),
"grant_type": "authorization_code"
}
}
if decoder:
args["decoder"] = decoder
frappe.local._response = redirect("/")
session = flow.get_auth_session(**args)
login_oauth_user(info, oauth_provider="google")
api_endpoint = oauth2_providers[provider].get("api_endpoint")
info = session.get(api_endpoint).json()
# because of a GET request!
frappe.db.commit()
print info
@frappe.whitelist(allow_guest=True)
def login_via_facebook(data):
data = json.loads(data)
if "verified_email" in info and not info.get("verified_email"):
frappe.throw("{verify}: {provider}".format(
verify=_("Error. Please verify your email with"),
provider=provider.title()))
if not (data.get("id") and data.get("fb_access_token")):
raise frappe.ValidationError

if not get_fb_userid(data.get("fb_access_token")):
# garbage
raise frappe.ValidationError
login_oauth_user(data, oauth_provider="facebook")
login_oauth_user(info, provider=provider)
def login_oauth_user(data, oauth_provider=None):
def login_oauth_user(data, provider=None):
user = data["email"]
if not frappe.db.exists("Profile", user):
create_oauth_user(data, oauth_provider)
create_oauth_user(data, provider)
frappe.local._response = redirect("/")
frappe.local.login_manager.user = user
frappe.local.login_manager.post_login()
def create_oauth_user(data, oauth_provider):
# because of a GET request!
frappe.db.commit()
def create_oauth_user(data, provider):
if data.get("birthday"):
b = data.get("birthday").split("/")
data["birthday"] = b[2] + "-" + b[0] + "-" + b[1]
from frappe.utils.dateutils import parse_date
data["birthday"] = parse_date(data["birthday"])
if isinstance(data.get("location"), dict):
data["location"] = data.get("location").get("name")
profile = frappe.bean({
"doctype":"Profile",
"first_name": data.get("first_name") or data.get("given_name"),
"first_name": data.get("first_name") or data.get("given_name") or data.get("name"),
"last_name": data.get("last_name") or data.get("family_name"),
"email": data["email"],
"gender": data.get("gender"),
"enabled": 1,
"new_password": frappe.generate_hash(data["email"]),
"location": data.get("location", {}).get("name"),
"location": data.get("location"),
"birth_date": data.get("birthday"),
"user_type": "Website User",
"user_image": data.get("picture")
"user_image": data.get("picture") or data.get("avatar_url")
})
if oauth_provider=="facebook":
if provider=="facebook":
profile.doc.fields.update({
"fb_username": data["username"],
"fb_userid": data["id"]
"fb_userid": data["id"],
"user_image": "https://graph.facebook.com/{username}/picture".format(username=data["username"])
})
elif oauth_provider=="google":
elif provider=="google":
profile.doc.google_userid = data["id"]
elif provider=="github":
profile.doc.github_userid = data["id"]
profile.doc.github_username = data["login"]
profile.ignore_permissions = True
profile.get_controller().no_welcome_mail = True
profile.insert()

def get_fb_userid(fb_access_token):
import requests
response = requests.get("https://graph.facebook.com/me?access_token=" + fb_access_token)
if response.status_code==200:
print response.json()
return response.json().get("id")
else:
return frappe.AuthenticationError

+ 1
- 1
frappe/website/doctype/blog_category/blog_category.py Voir le fichier

@@ -15,7 +15,7 @@ class DocType(WebsiteGenerator):
self.doc.name = self.doc.category_name
def get_page_title(self):
return self.doc.title
return self.doc.title or self.doc.name
def on_update(self):
WebsiteGenerator.on_update(self)


+ 2
- 1
frappe/website/render.py Voir le fichier

@@ -76,7 +76,8 @@ def build_page(path):
return html
def is_ajax():
return frappe.get_request_header("X-Requested-With")=="XMLHttpRequest"
return (frappe.get_request_header("X-Requested-With")=="XMLHttpRequest"
if hasattr(frappe.local, "_response") else False)
def resolve_path(path):
if not path:


+ 0
- 2
frappe/website/sitemap.py Voir le fichier

@@ -73,5 +73,3 @@ def set_sidebar_items(sitemap_options, pathname, home_page):
and t1.docname = t2.name
order by t2.{sort_by} {sort_order}""".format(**website_template.fields),
pathname, as_dict=True)
print sitemap_options.children

+ 1
- 1
requirements.txt Voir le fichier

@@ -1,7 +1,6 @@
chardet
cssmin
dropbox
oauth2client
gunicorn
httplib2
jinja2
@@ -20,3 +19,4 @@ werkzeug
semantic_version
lxml
inlinestyler
rauth

Chargement…
Annuler
Enregistrer