diff --git a/frappe/core/doctype/profile/profile.py b/frappe/core/doctype/profile/profile.py
index 0d281b9a0e..71cbf5aedc 100644
--- a/frappe/core/doctype/profile/profile.py
+++ b/frappe/core/doctype/profile/profile.py
@@ -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
diff --git a/frappe/core/doctype/profile/profile.txt b/frappe/core/doctype/profile/profile.txt
index 63cd6407b3..9fdd7675c1 100644
--- a/frappe/core/doctype/profile/profile.txt
+++ b/frappe/core/doctype/profile/profile.txt
@@ -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",
diff --git a/frappe/data/sample_site_config.json b/frappe/data/sample_site_config.json
new file mode 100644
index 0000000000..93f4d07147
--- /dev/null
+++ b/frappe/data/sample_site_config.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/frappe/templates/includes/login.js b/frappe/templates/includes/login.js
index d6b1275fcb..2d6d5b0246 100644
--- a/frappe/templates/includes/login.js
+++ b/frappe/templates/includes/login.js
@@ -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, sign-up 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 %}
diff --git a/frappe/templates/pages/login.html b/frappe/templates/pages/login.html
index 8b3616a560..4325a195f0 100644
--- a/frappe/templates/pages/login.html
+++ b/frappe/templates/pages/login.html
@@ -13,19 +13,25 @@
-->
- {%- if fb_app_id is defined -%}
-
-
or
- + {%- if third_party_sign_in -%} +or login via
++ {%- if facebook_sign_in is defined %} + + {{ _("Facebook") }} + {%- endif -%} + + {%- if google_sign_in is defined %} + + {{ _("Google") }} + {%- endif -%} + + {%- if github_sign_in is defined %} + + {{ _("GitHub") }} + {%- endif -%} +
{%- endif -%} - {%- if google_sign_in is defined %} --
or
- - {{ _("Login via Google") }} - {%- endif -%} diff --git a/frappe/templates/pages/login.py b/frappe/templates/pages/login.py index 7edd544e8e..b57089f3f2 100644 --- a/frappe/templates/pages/login.py +++ b/frappe/templates/pages/login.py @@ -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 \ No newline at end of file diff --git a/frappe/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py index 8d97e47888..492ca04eb6 100644 --- a/frappe/website/doctype/blog_category/blog_category.py +++ b/frappe/website/doctype/blog_category/blog_category.py @@ -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) diff --git a/frappe/website/render.py b/frappe/website/render.py index 3128fe0a7e..f02d7d9591 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -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: diff --git a/frappe/website/sitemap.py b/frappe/website/sitemap.py index 5b1efb829f..a09b4acf03 100644 --- a/frappe/website/sitemap.py +++ b/frappe/website/sitemap.py @@ -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 diff --git a/requirements.txt b/requirements.txt index 5c8b554743..2c8a0b93ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ chardet cssmin dropbox -oauth2client gunicorn httplib2 jinja2 @@ -20,3 +19,4 @@ werkzeug semantic_version lxml inlinestyler +rauth