diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py
index 87f2b01614..98d17114f9 100644
--- a/frappe/config/integrations.py
+++ b/frappe/config/integrations.py
@@ -44,7 +44,7 @@ def get_data():
"items": [
{
"type": "doctype",
- "name": "Social Login Keys",
+ "name": "Social Login Key",
"description": _("Enter keys to enable login via Facebook, Google, GitHub."),
},
{
diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py
index 7d25254e4b..42f99b0bc4 100644
--- a/frappe/core/doctype/user/test_user.py
+++ b/frappe/core/doctype/user/test_user.py
@@ -26,6 +26,10 @@ class TestUser(unittest.TestCase):
first_name='Tester')).insert()
self.assertEquals(new_user.user_type, 'Website User')
+ # social login userid for frappe
+ self.assertTrue(new_user.social_logins[0].userid)
+ self.assertEquals(new_user.social_logins[0].provider, "frappe")
+
# role with desk access
new_user.add_roles('_Test Role 2')
new_user.save()
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index 6d6eb6748d..5a2b1f5332 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -1851,95 +1851,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "fb_username",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Facebook Username",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "fb_userid",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Facebook User ID",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "google_userid",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Google User ID",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_49",
- "fieldtype": "Column Break",
+ "fieldname": "social_logins",
+ "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -1947,8 +1860,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
+ "label": "Social Logins",
"length": 0,
"no_copy": 0,
+ "options": "User Social Login",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -1961,94 +1876,6 @@
"set_only_once": 0,
"unique": 0
},
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "github_userid",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Github User ID",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "github_username",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Github Username",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "frappe_userid",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Frappe User ID",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -2124,7 +1951,7 @@
"istable": 0,
"max_attachments": 5,
"menu_index": 0,
- "modified": "2017-11-15 13:01:00.085916",
+ "modified": "2017-12-29 14:37:57.759229",
"modified_by": "Administrator",
"module": "Core",
"name": "User",
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index 5e9d33bc67..7f5375b117 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -75,8 +75,8 @@ class User(Document):
if self.language == "Loading...":
self.language = None
- if (self.name not in ["Administrator", "Guest"]) and (not self.frappe_userid):
- self.frappe_userid = frappe.generate_hash(length=39)
+ if (self.name not in ["Administrator", "Guest"]) and (not self.get_social_login_userid("frappe")):
+ self.set_social_login_userid("frappe", frappe.generate_hash(length=39))
def validate_roles(self):
if self.role_profile_name:
@@ -494,6 +494,25 @@ class User(Document):
if len(email_accounts) != len(set(email_accounts)):
frappe.throw(_("Email Account added multiple times"))
+ def get_social_login_userid(self, provider):
+ try:
+ for p in self.social_logins:
+ if p.provider == provider:
+ return p.userid
+ except:
+ return None
+
+ def set_social_login_userid(self, provider, userid, username=None):
+ social_logins = {
+ "provider": provider,
+ "userid": userid
+ }
+
+ if username:
+ social_logins["username"] = username
+
+ self.append("social_logins", social_logins)
+
@frappe.whitelist()
def get_timezones():
import pytz
diff --git a/frappe/integrations/doctype/social_login_keys/__init__.py b/frappe/core/doctype/user_social_login/__init__.py
similarity index 100%
rename from frappe/integrations/doctype/social_login_keys/__init__.py
rename to frappe/core/doctype/user_social_login/__init__.py
diff --git a/frappe/core/doctype/user_social_login/user_social_login.json b/frappe/core/doctype/user_social_login/user_social_login.json
new file mode 100644
index 0000000000..3cac838016
--- /dev/null
+++ b/frappe/core/doctype/user_social_login/user_social_login.json
@@ -0,0 +1,189 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2017-12-02 13:01:20.507112",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "provider",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Provider",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_0",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "username",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Username",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_0",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "userid",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "User ID",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2017-12-02 15:37:58.397062",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "User Social Login",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/user_social_login/user_social_login.py b/frappe/core/doctype/user_social_login/user_social_login.py
new file mode 100644
index 0000000000..cc6c3d0e05
--- /dev/null
+++ b/frappe/core/doctype/user_social_login/user_social_login.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe.model.document import Document
+
+class UserSocialLogin(Document):
+ pass
diff --git a/frappe/docs/assets/img/social_login_key.png b/frappe/docs/assets/img/social_login_key.png
new file mode 100644
index 0000000000..4ef84703c3
Binary files /dev/null and b/frappe/docs/assets/img/social_login_key.png differ
diff --git a/frappe/docs/user/en/guides/app-development/adding-social-login-provider.md b/frappe/docs/user/en/guides/app-development/adding-social-login-provider.md
new file mode 100644
index 0000000000..0a517af14b
--- /dev/null
+++ b/frappe/docs/user/en/guides/app-development/adding-social-login-provider.md
@@ -0,0 +1,49 @@
+# Adding Social Login Provider
+
+This guide discusses how to add a social login provider to frappe via pull request.
+
+### Add your provider in `SocialLoginKey.get_social_login_provider`
+
+```
+providers["Frappe"] = {
+ "provider_name": "Frappe",
+ "enable_social_login": 1,
+ "custom_base_url": 1,
+ "icon":"/assets/frappe/images/favicon.png",
+ "redirect_url": "/api/method/frappe.www.login.login_via_frappe",
+ "api_endpoint": "/api/method/frappe.integrations.oauth2.openid_profile",
+ "api_endpoint_args":None,
+ "authorize_url": "/api/method/frappe.integrations.oauth2.authorize",
+ "access_token_url": "/api/method/frappe.integrations.oauth2.get_token",
+ "auth_url_data": json.dumps({
+ "response_type": "code",
+ "scope": "openid"
+ })
+}
+```
+
+### Add provider key in exact same type case in options of `social_login_provider` select field on `Social Login Key` DocType. e.g. `Frappe`
+
+Once the user adds a social login provider and enables it the `Authorization Code` is sent back by the provider api server on to the redirect_url mentioned on the same server. You will have to add a whitelisted method allowing guest access in `frappe.integrations.oauth2_logins`. e.g. `login_via_office365`
+
+There many implementations of OAuth 2.0 + OpenID Connect. Here we'll discuss two ways of accessing openid information.
+
+#### User Creation via OpenID Profile Endpoint
+
+example:
+
+```
+@frappe.whitelist(allow_guest=True)
+def login_via_frappe(code, state):
+ login_via_oauth2("frappe", code, state, decoder=json.loads)
+```
+
+#### User Creation via id_token
+
+example:
+
+```
+@frappe.whitelist(allow_guest=True)
+def login_via_office365(code, state):
+ login_via_oauth2_id_token("office_365", code, state, decoder=json.loads)
+```
diff --git a/frappe/docs/user/en/guides/app-development/index.txt b/frappe/docs/user/en/guides/app-development/index.txt
index 5a9b4cc4a9..6215b3d73a 100755
--- a/frappe/docs/user/en/guides/app-development/index.txt
+++ b/frappe/docs/user/en/guides/app-development/index.txt
@@ -13,3 +13,4 @@ single-type-doctype
trigger-event-on-deletion-of-grid-row
dialogs-types
using-html-templates-in-javascript
+adding-social-login-provider
diff --git a/frappe/docs/user/en/guides/deployment/how-to-enable-social-logins.md b/frappe/docs/user/en/guides/deployment/how-to-enable-social-logins.md
index bd16e88037..b35a5cc976 100755
--- a/frappe/docs/user/en/guides/deployment/how-to-enable-social-logins.md
+++ b/frappe/docs/user/en/guides/deployment/how-to-enable-social-logins.md
@@ -63,4 +63,20 @@ To enable these signups, you need to have **Client ID** and **Client Secret** fr
+---
+
+### Office 365
+
+1. Go to [https://portal.azure.com](https://portal.azure.com)
+1. Create a new Azure Active Directory > App Registration.
+1. Click on New Application Registration
+1. Fill the form with:
+ - Application Name
+ - Application Type - Web app / API
+ - Single Sign-on URL as
+ **http://{{ yoursite }}/api/method/frappe.www.login.login\_via\_office365**
+1. Enable Multi Tenent for the added App.
+1. Go to the section **Application ID** and copy the Client ID and copy Client Secret by adding new password into Social Login Key
+
+---
diff --git a/frappe/docs/user/en/guides/integration/index.txt b/frappe/docs/user/en/guides/integration/index.txt
index a2c88ed7d3..4aaf38daca 100755
--- a/frappe/docs/user/en/guides/integration/index.txt
+++ b/frappe/docs/user/en/guides/integration/index.txt
@@ -3,3 +3,4 @@ how_to_setup_oauth
using_oauth
openid_connect_and_frappe_social_login
google_gsuite
+social_login_key
diff --git a/frappe/docs/user/en/guides/integration/social_login_key.md b/frappe/docs/user/en/guides/integration/social_login_key.md
new file mode 100644
index 0000000000..8bc3d030db
--- /dev/null
+++ b/frappe/docs/user/en/guides/integration/social_login_key.md
@@ -0,0 +1,26 @@
+# Social Login Key
+
+Add social login providers like Facebook, Frappe, Github, Google, Microsoft, etc and enable social login.
+
+#### Setup Social Logins
+
+To add Social Login Key go to
+
+> Integrations > Authentication > Social Login Key
+
+Social Login Key
+
+
+
+1. Select the Social Login Provider or select "Custom"
+2. If required for provider enter "Base URL"
+3. To enable check "Enable Social Login" to show Icon on login screen
+4. Also add Client ID and Client Secret as per provider.
+
+e.g. Social Login Key
+
+- **Social Login Provider** : `Frappe`
+- **Client ID** : `ABCDEFG`
+- **Client Secret** : `123456`
+- **Enable Social Login** : `Check`
+- **Base URL** : `https://erpnext.org` (required for some providers)
diff --git a/frappe/integrations/doctype/social_login_key/__init__.py b/frappe/integrations/doctype/social_login_key/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.js b/frappe/integrations/doctype/social_login_key/social_login_key.js
new file mode 100644
index 0000000000..e2cbb3459f
--- /dev/null
+++ b/frappe/integrations/doctype/social_login_key/social_login_key.js
@@ -0,0 +1,78 @@
+// Copyright (c) 2017, Frappe Technologies and contributors
+// For license information, please see license.txt
+const fields = [
+ "provider_name", "base_url", "custom_base_url",
+ "icon", "authorize_url", "access_token_url", "redirect_url",
+ "api_endpoint", "api_endpoint_args", "auth_url_data"
+];
+
+frappe.ui.form.on('Social Login Key', {
+ refresh(frm) {
+ frm.trigger("setup_fields");
+ },
+
+ custom_base_url(frm) {
+ frm.trigger("setup_fields");
+ },
+
+ social_login_provider(frm) {
+ if(frm.doc.social_login_provider != "Custom") {
+ frappe.call({
+ "doc": frm.doc,
+ "method": "get_social_login_provider",
+ "args": {
+ "provider": frm.doc.social_login_provider
+ }
+ }).done((r) => {
+ const provider = r.message;
+ for(var field of fields) {
+ frm.set_value(field, provider[field]);
+ frm.set_df_property(field, "read_only", 1);
+ if (frm.doc.custom_base_url) {
+ frm.toggle_enable("base_url", 1);
+ }
+ }
+ });
+ } else {
+ frm.trigger("clear_fields");
+ frm.trigger("setup_fields");
+ }
+ },
+
+ setup_fields(frm) {
+ // set custom_base_url to read only for "Custom" provider
+ if(frm.doc.social_login_provider == "Custom") {
+ frm.set_value("custom_base_url", 1);
+ frm.set_df_property("custom_base_url", "read_only", 1);
+ }
+
+ // set fields to read only for providers from template
+ for(var f of fields) {
+ if(frm.doc.social_login_provider != "Custom"){
+ frm.set_df_property(f, "read_only", 1);
+ }
+ }
+
+ // enable base_url for providers with custom_base_url
+ if(frm.doc.custom_base_url) {
+ frm.set_df_property("base_url", "read_only", 0);
+ frm.fields_dict["sb_identity_details"].collapse(false);
+ }
+
+ // hide social_login_provider and provider_name for non local
+ if(!frm.doc.__islocal &&
+ (frm.doc.social_login_provider ||
+ frm.doc.provider_name)) {
+ frm.set_df_property("social_login_provider", "hidden", 1);
+ frm.set_df_property("provider_name", "hidden", 1);
+ }
+ },
+
+ clear_fields(frm) {
+ for(var field of fields){
+ frm.set_value(field, "");
+ frm.set_df_property(field, "read_only", 0);
+ }
+ }
+
+});
diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.json b/frappe/integrations/doctype/social_login_key/social_login_key.json
new file mode 100644
index 0000000000..8475a8281b
--- /dev/null
+++ b/frappe/integrations/doctype/social_login_key/social_login_key.json
@@ -0,0 +1,700 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2017-11-18 15:36:09.676722",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "enable_social_login",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Enable Social Login",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.enable_social_login",
+ "columns": 0,
+ "fieldname": "client_credentials",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Client Credentials",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Custom",
+ "depends_on": "eval:doc.custom!=1",
+ "fieldname": "social_login_provider",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Social Login Provider",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Custom\nFacebook\nFrappe\nGitHub\nGoogle\nOffice 365",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 1,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "client_id",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Client ID",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_0",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "provider_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Provider Name",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 1,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "client_secret",
+ "fieldtype": "Password",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Client Secret",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.custom_base_url",
+ "columns": 0,
+ "depends_on": "",
+ "fieldname": "sb_identity_details",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Identity Details",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "icon",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Icon",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_1",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "base_url",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Base URL",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.social_login_provider===\"Custom\"",
+ "columns": 0,
+ "depends_on": "",
+ "fieldname": "client_urls",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Client URLs",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "authorize_url",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Authorize URL",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "access_token_url",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Access Token URL",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "redirect_url",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Redirect URL",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "api_endpoint",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "API Endpoint",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "custom_base_url",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Custom Base URL",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.social_login_provider===\"Custom\"",
+ "columns": 0,
+ "depends_on": "",
+ "fieldname": "client_information",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Client Information",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "api_endpoint_args",
+ "fieldtype": "Code",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "API Endpoint Args",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "auth_url_data",
+ "fieldtype": "Code",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Auth URL Data",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-12-21 13:55:17.041059",
+ "modified_by": "Administrator",
+ "module": "Integrations",
+ "name": "Social Login Key",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "provider_name",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.py b/frappe/integrations/doctype/social_login_key/social_login_key.py
new file mode 100644
index 0000000000..305da2391e
--- /dev/null
+++ b/frappe/integrations/doctype/social_login_key/social_login_key.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe, json
+from frappe import _
+from frappe.model.document import Document
+
+class BaseUrlNotSetError(frappe.ValidationError): pass
+class AuthorizeUrlNotSetError(frappe.ValidationError): pass
+class AccessTokenUrlNotSetError(frappe.ValidationError): pass
+class RedirectUrlNotSetError(frappe.ValidationError): pass
+class ClientIDNotSetError(frappe.ValidationError): pass
+class ClientSecretNotSetError(frappe.ValidationError): pass
+
+class SocialLoginKey(Document):
+
+ def autoname(self):
+ self.name = frappe.scrub(self.provider_name)
+
+ def validate(self):
+ if self.custom_base_url and not self.base_url:
+ frappe.throw(_("Please enter Base URL"), exc=BaseUrlNotSetError)
+ if not self.authorize_url:
+ frappe.throw(_("Please enter Authorize URL"), exc=AuthorizeUrlNotSetError)
+ if not self.access_token_url:
+ frappe.throw(_("Please enter Access Token URL"), exc=AccessTokenUrlNotSetError)
+ if not self.redirect_url:
+ frappe.throw(_("Please enter Redirect URL"), exc=RedirectUrlNotSetError)
+ if self.enable_social_login and not self.client_id:
+ frappe.throw(_("Please enter Client ID before social login is enabled"), exc=ClientIDNotSetError)
+ if self.enable_social_login and not self.client_secret:
+ frappe.throw(_("Please enter Client Secret before social login is enabled"), exc=ClientSecretNotSetError)
+
+ def get_social_login_provider(self, provider, initialize=False):
+ providers = {}
+
+ providers["Office 365"] = {
+ "provider_name": "Office 365",
+ "enable_social_login": 1,
+ "base_url": "https://login.microsoftonline.com",
+ "custom_base_url": 0,
+ "icon":"fa fa-windows",
+ "authorize_url": "https://login.microsoftonline.com/common/oauth2/authorize",
+ "access_token_url": "https://login.microsoftonline.com/common/oauth2/token",
+ "redirect_url": "/api/method/frappe.integrations.oauth2_logins.login_via_office365",
+ "api_endpoint": None,
+ "api_endpoint_args":None,
+ "auth_url_data": json.dumps({
+ "response_type": "code",
+ "scope":"openid"
+ })
+ }
+
+ providers["GitHub"] = {
+ "provider_name":"GitHub",
+ "enable_social_login": 1,
+ "base_url":"https://api.github.com/",
+ "custom_base_url":0,
+ "icon":"fa fa-github",
+ "authorize_url":"https://github.com/login/oauth/authorize",
+ "access_token_url":"https://github.com/login/oauth/access_token",
+ "redirect_url":"/api/method/frappe.www.login.login_via_github",
+ "api_endpoint":"user",
+ "api_endpoint_args":None,
+ "auth_url_data":None
+ }
+
+ providers["Google"] = {
+ "provider_name": "Google",
+ "enable_social_login": 1,
+ "base_url": "https://www.googleapis.com",
+ "custom_base_url": 0,
+ "icon":"fa fa-google",
+ "authorize_url": "https://accounts.google.com/o/oauth2/auth",
+ "access_token_url": "https://accounts.google.com/o/oauth2/token",
+ "redirect_url": "/api/method/frappe.www.login.login_via_google",
+ "api_endpoint": "oauth2/v2/userinfo",
+ "api_endpoint_args":None,
+ "auth_url_data": json.dumps({
+ "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
+ "response_type": "code"
+ })
+ }
+
+ providers["Facebook"] = {
+ "provider_name": "Facebook",
+ "enable_social_login": 1,
+ "base_url": "https://graph.facebook.com",
+ "custom_base_url": 0,
+ "icon": "fa fa-facebook",
+ "authorize_url": "https://www.facebook.com/dialog/oauth",
+ "access_token_url": "https://graph.facebook.com/oauth/access_token",
+ "redirect_url": "/api/method/frappe.www.login.login_via_facebook",
+ "api_endpoint": "/v2.5/me",
+ "api_endpoint_args": json.dumps({
+ "fields": "first_name,last_name,email,gender,location,verified,picture"
+ }),
+ "auth_url_data": json.dumps({
+ "display": "page",
+ "response_type": "code",
+ "scope": "email,public_profile"
+ })
+ }
+
+ providers["Frappe"] = {
+ "provider_name": "Frappe",
+ "enable_social_login": 1,
+ "custom_base_url": 1,
+ "icon":"/assets/frappe/images/favicon.png",
+ "redirect_url": "/api/method/frappe.www.login.login_via_frappe",
+ "api_endpoint": "/api/method/frappe.integrations.oauth2.openid_profile",
+ "api_endpoint_args":None,
+ "authorize_url": "/api/method/frappe.integrations.oauth2.authorize",
+ "access_token_url": "/api/method/frappe.integrations.oauth2.get_token",
+ "auth_url_data": json.dumps({
+ "response_type": "code",
+ "scope": "openid"
+ })
+ }
+
+ # Initialize the doc and return, used in patch
+ # Or can be used for creating key from controller
+ if initialize and provider:
+ for k, v in providers[provider].items():
+ setattr(self,k,v)
+ return
+
+ return providers.get(provider) if provider else providers
diff --git a/frappe/integrations/doctype/social_login_key/test_social_login_key.js b/frappe/integrations/doctype/social_login_key/test_social_login_key.js
new file mode 100644
index 0000000000..86aad7ab64
--- /dev/null
+++ b/frappe/integrations/doctype/social_login_key/test_social_login_key.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Social Login Key", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Social Login Key
+ () => frappe.tests.make('Social Login Key', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/frappe/integrations/doctype/social_login_key/test_social_login_key.py b/frappe/integrations/doctype/social_login_key/test_social_login_key.py
new file mode 100644
index 0000000000..58bd48d64a
--- /dev/null
+++ b/frappe/integrations/doctype/social_login_key/test_social_login_key.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+from frappe.integrations.doctype.social_login_key.social_login_key import BaseUrlNotSetError
+import unittest
+
+class TestSocialLoginKey(unittest.TestCase):
+ def test_adding_frappe_social_login_provider(self):
+ provider_name = "Frappe"
+ social_login_key = make_social_login_key(
+ social_login_provider=provider_name
+ )
+ social_login_key.get_social_login_provider(provider_name, initialize=True)
+ self.assertRaises(BaseUrlNotSetError, social_login_key.insert)
+
+def make_social_login_key(**kwargs):
+ kwargs["doctype"] = "Social Login Key"
+ if not "provider_name" in kwargs:
+ kwargs["provider_name"] = "Test OAuth2 Provider"
+ doc = frappe.get_doc(kwargs)
+ return doc
diff --git a/frappe/integrations/doctype/social_login_keys/social_login_keys.js b/frappe/integrations/doctype/social_login_keys/social_login_keys.js
deleted file mode 100644
index 52ced2447a..0000000000
--- a/frappe/integrations/doctype/social_login_keys/social_login_keys.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Social Login Keys', {
- refresh: function(frm) {
-
- }
-});
diff --git a/frappe/integrations/doctype/social_login_keys/social_login_keys.json b/frappe/integrations/doctype/social_login_keys/social_login_keys.json
deleted file mode 100644
index 9ce8c3ad79..0000000000
--- a/frappe/integrations/doctype/social_login_keys/social_login_keys.json
+++ /dev/null
@@ -1,414 +0,0 @@
-{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2014-03-04 08:29:52",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "System",
- "editable_grid": 0,
- "fields": [
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "facebook",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Facebook",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "facebook_client_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Facebook Client ID",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "facebook_client_secret",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Facebook Client Secret",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "google",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Google",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "google_client_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Google Client ID",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "google_client_secret",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Google Client Secret",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "github",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "GitHub",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "github_client_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "GitHub Client ID",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "github_client_secret",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "GitHub Client Secret",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "frappe",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Frappe",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "frappe_client_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Frappe Client ID",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "frappe_client_secret",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Frappe Client Secret",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "frappe_server_url",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Frappe Server URL",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "icon-signin",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "in_dialog": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-12-29 14:40:30.397643",
- "modified_by": "Administrator",
- "module": "Integrations",
- "name": "Social Login Keys",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/social_login_keys/social_login_keys.py b/frappe/integrations/doctype/social_login_keys/social_login_keys.py
deleted file mode 100644
index 33c8ab2560..0000000000
--- a/frappe/integrations/doctype/social_login_keys/social_login_keys.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
-
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-import requests
-import socket
-
-from frappe.model.document import Document
-from frappe import _
-from six.moves.urllib.parse import urlparse
-
-class SocialLoginKeys(Document):
- def validate(self):
- self.validate_frappe_server_url()
-
- def validate_frappe_server_url(self):
- if self.frappe_server_url:
- if self.frappe_server_url.endswith('/'):
- self.frappe_server_url = self.frappe_server_url[:-1]
-
- try:
- frappe_server_hostname = urlparse(self.frappe_server_url).netloc
- except:
- frappe.throw(_("Check Frappe Server URL"))
-
- if socket.gethostname() != frappe_server_hostname or \
- (frappe.local.conf.domains is not None) and \
- (frappe_server_hostname not in frappe.local.conf.domains):
- try:
- requests.get(self.frappe_server_url + "/api/method/frappe.handler.version", timeout=5)
- except:
- frappe.throw(_("Unable to make request to the Frappe Server URL"))
diff --git a/frappe/integrations/oauth2.py b/frappe/integrations/oauth2.py
index 7a5b616395..5bc7f33094 100644
--- a/frappe/integrations/oauth2.py
+++ b/frappe/integrations/oauth2.py
@@ -103,9 +103,9 @@ def get_token(*args, **kwargs):
headers = r.headers
#Check whether frappe server URL is set
- frappe_server_url = frappe.db.get_value("Social Login Keys", None, "frappe_server_url") or None
+ frappe_server_url = frappe.db.get_value("Social Login Key", "frappe", "base_url") or None
if not frappe_server_url:
- frappe.throw(_("Define Frappe Server URL in Social Login Keys"))
+ frappe.throw(_("Please set Base URL in Social Login Key for Frappe"))
try:
headers, body, status = get_oauth_server().create_token_response(uri, http_method, body, headers, frappe.flags.oauth_credentials)
@@ -124,7 +124,7 @@ def get_token(*args, **kwargs):
id_token = {
"aud": token_client,
"exp": int((frappe.db.get_value("OAuth Bearer Token", out.access_token, "expiration_time") - frappe.utils.datetime.datetime(1970, 1, 1)).total_seconds()),
- "sub": frappe.db.get_value("User", token_user, "frappe_userid"),
+ "sub": frappe.db.get_value("User Social Login", {"parent":token_user, "provider": "frappe"}, "userid"),
"iss": frappe_server_url,
"at_hash": frappe.oauth.calculate_at_hash(out.access_token, hashlib.sha256)
}
@@ -156,7 +156,8 @@ def revoke_token(*args, **kwargs):
@frappe.whitelist()
def openid_profile(*args, **kwargs):
picture = None
- first_name, last_name, avatar, name, frappe_userid = frappe.db.get_value("User", frappe.session.user, ["first_name", "last_name", "user_image", "name", "frappe_userid"])
+ first_name, last_name, avatar, name = frappe.db.get_value("User", frappe.session.user, ["first_name", "last_name", "user_image", "name"])
+ frappe_userid = frappe.db.get_value("User Social Login", {"parent":frappe.session.user, "provider": "frappe"}, "userid")
request_url = urlparse(frappe.request.url)
if avatar:
diff --git a/frappe/integrations/oauth2_logins.py b/frappe/integrations/oauth2_logins.py
new file mode 100644
index 0000000000..666a0030ef
--- /dev/null
+++ b/frappe/integrations/oauth2_logins.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+import frappe.utils
+from frappe.utils.oauth import login_via_oauth2, login_via_oauth2_id_token
+import json
+
+@frappe.whitelist(allow_guest=True)
+def login_via_google(code, state):
+ login_via_oauth2("google", code, state, decoder=json.loads)
+
+@frappe.whitelist(allow_guest=True)
+def login_via_github(code, state):
+ login_via_oauth2("github", code, state)
+
+@frappe.whitelist(allow_guest=True)
+def login_via_facebook(code, state):
+ login_via_oauth2("facebook", code, state, decoder=json.loads)
+
+@frappe.whitelist(allow_guest=True)
+def login_via_frappe(code, state):
+ login_via_oauth2("frappe", code, state, decoder=json.loads)
+
+@frappe.whitelist(allow_guest=True)
+def login_via_office365(code, state):
+ login_via_oauth2_id_token("office_365", code, state, decoder=json.loads)
diff --git a/frappe/oauth.py b/frappe/oauth.py
index 61b4db5034..e7944da06b 100644
--- a/frappe/oauth.py
+++ b/frappe/oauth.py
@@ -387,7 +387,7 @@ class OAuthWebRequestValidator(RequestValidator):
- OpenIDConnectImplicit
- OpenIDConnectHybrid
"""
- if id_token_hint and id_token_hint == frappe.get_value("User", frappe.session.user, "frappe_userid"):
+ if id_token_hint and id_token_hint == frappe.db.get_value("User Social Login", {"parent":frappe.session.user, "provider": "frappe"}, "userid"):
return True
else:
return False
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 49bc7bb978..a1e79e62c8 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -200,4 +200,5 @@ frappe.patches.v9_1.resave_domain_settings
frappe.patches.v9_1.revert_domain_settings
frappe.patches.v9_1.move_feed_to_activity_log
execute:frappe.delete_doc('Page', 'data-import-tool', ignore_missing=True)
-frappe.patches.v10_0.reload_countries_and_currencies
\ No newline at end of file
+frappe.patches.v10_0.reload_countries_and_currencies
+frappe.patches.v10_0.refactor_social_login_keys
diff --git a/frappe/patches/v10_0/refactor_social_login_keys.py b/frappe/patches/v10_0/refactor_social_login_keys.py
new file mode 100644
index 0000000000..9f1f0b4afc
--- /dev/null
+++ b/frappe/patches/v10_0/refactor_social_login_keys.py
@@ -0,0 +1,91 @@
+# see license
+import frappe
+
+def execute():
+ # Move User Data into DocType
+ frappe.reload_doc("core", "doctype", "user", force=True)
+ frappe.reload_doc("core", "doctype", "user_social_login", force=True)
+ users = frappe.get_all("User", filters=[["username", "not in", ["Guest","Administrator"]]])
+ for u in users:
+ user = frappe.get_doc("User", u.get("name"))
+ save = False
+
+ if user.fb_userid and user.fb_username:
+ user.append("social_logins", {
+ "provider": "facebook",
+ "userid": user.fb_userid,
+ "username": user.fb_username
+ })
+ save = True
+
+ if user.frappe_userid:
+ user.append("social_logins", {
+ "provider": "frappe",
+ "userid": user.frappe_userid
+ })
+ save = True
+
+ if user.github_userid and user.github_username:
+ user.append("social_logins", {
+ "provider": "github",
+ "userid": user.github_userid,
+ "username": user.github_username,
+ })
+ save = True
+
+ if user.google_userid:
+ user.append("social_logins", {
+ "provider": "google",
+ "userid": user.google_userid,
+ })
+ save = True
+
+ if save:
+ user.save()
+
+ # Create Social Login Key(s) from Social Login Keys
+ frappe.reload_doc("integrations", "doctype", "social_login_key", force=True)
+
+ social_login_keys = frappe.get_doc("Social Login Keys", "Social Login Keys")
+ if social_login_keys.facebook_client_id or social_login_keys.facebook_client_secret:
+ facebook_login_key = frappe.new_doc("Social Login Key")
+ facebook_login_key.get_social_login_provider("Facebook", initialize=True)
+ facebook_login_key.social_login_provider = "Facebook"
+ facebook_login_key.client_id = social_login_keys.facebook_client_id
+ facebook_login_key.client_secret = social_login_keys.facebook_client_secret
+ if not (facebook_login_key.client_secret and facebook_login_key.client_id):
+ facebook_login_key.enable_social_login = 0
+ facebook_login_key.save()
+
+ if social_login_keys.frappe_server_url:
+ frappe_login_key = frappe.new_doc("Social Login Key")
+ frappe_login_key.get_social_login_provider("Frappe", initialize=True)
+ frappe_login_key.social_login_provider = "Frappe"
+ frappe_login_key.base_url = social_login_keys.frappe_server_url
+ frappe_login_key.client_id = social_login_keys.frappe_client_id
+ frappe_login_key.client_secret = social_login_keys.frappe_client_secret
+ if not (frappe_login_key.client_secret and frappe_login_key.client_id and frappe_login_key.base_url):
+ frappe_login_key.enable_social_login = 0
+ frappe_login_key.save()
+
+ if social_login_keys.github_client_id or social_login_keys.github_client_secret:
+ github_login_key = frappe.new_doc("Social Login Key")
+ github_login_key.get_social_login_provider("GitHub", initialize=True)
+ github_login_key.social_login_provider = "GitHub"
+ github_login_key.client_id = social_login_keys.github_client_id
+ github_login_key.client_secret = social_login_keys.github_client_secret
+ if not (github_login_key.client_secret and github_login_key.client_id):
+ github_login_key.enable_social_login = 0
+ github_login_key.save()
+
+ if social_login_keys.google_client_id or social_login_keys.google_client_secret:
+ google_login_key = frappe.new_doc("Social Login Key")
+ google_login_key.get_social_login_provider("Google", initialize=True)
+ google_login_key.social_login_provider = "Google"
+ google_login_key.client_id = social_login_keys.google_client_id
+ google_login_key.client_secret = social_login_keys.google_client_secret
+ if not (google_login_key.client_secret and google_login_key.client_id):
+ google_login_key.enable_social_login = 0
+ google_login_key.save()
+
+ frappe.delete_doc("DocType", "Social Login Keys")
diff --git a/frappe/tests/test_frappeoauth2client.py b/frappe/tests/test_frappeoauth2client.py
index df85c33ad0..ebf09adf6d 100644
--- a/frappe/tests/test_frappeoauth2client.py
+++ b/frappe/tests/test_frappeoauth2client.py
@@ -16,8 +16,13 @@ class TestFrappeOAuth2Client(unittest.TestCase):
self.client_id = frappe.get_all("OAuth Client", fields=["*"])[0].get("client_id")
# Set Frappe server URL reqired for id_token generation
- frappe.db.set_value("Social Login Keys", None, "frappe_server_url", "http://localhost:8000")
- frappe.db.commit()
+ try:
+ frappe_login_key = frappe.get_doc("Social Login Key", "frappe")
+ except frappe.DoesNotExistError:
+ frappe_login_key = frappe.new_doc("Social Login Key")
+ frappe_login_key.get_social_login_provider("Frappe", initialize=True)
+ frappe_login_key.base_url = "http://localhost:8000"
+ frappe_login_key.save()
def test_insert_note(self):
diff --git a/frappe/tests/ui/test_oauth20.py b/frappe/tests/ui/test_oauth20.py
index c6361fc39d..1cc864416e 100644
--- a/frappe/tests/ui/test_oauth20.py
+++ b/frappe/tests/ui/test_oauth20.py
@@ -15,8 +15,13 @@ class TestOAuth20(unittest.TestCase):
self.client_id = frappe.get_all("OAuth Client", fields=["*"])[0].get("client_id")
# Set Frappe server URL reqired for id_token generation
- frappe.db.set_value("Social Login Keys", None, "frappe_server_url", "http://localhost:8000")
- frappe.db.commit()
+ try:
+ frappe_login_key = frappe.get_doc("Social Login Key", "frappe")
+ except frappe.DoesNotExistError:
+ frappe_login_key = frappe.new_doc("Social Login Key")
+ frappe_login_key.get_social_login_provider("Frappe", initialize=True)
+ frappe_login_key.base_url = "http://localhost:8000"
+ frappe_login_key.save()
def test_login_using_authorization_code(self):
@@ -113,3 +118,6 @@ class TestOAuth20(unittest.TestCase):
self.assertTrue(response_url.get("expires_in"))
self.assertTrue(response_url.get("scope"))
self.assertTrue(response_url.get("token_type"))
+
+ def tearDown(self):
+ self.driver.close()
diff --git a/frappe/tests/ui/test_social_login_key_buttons.py b/frappe/tests/ui/test_social_login_key_buttons.py
new file mode 100644
index 0000000000..376d4139e4
--- /dev/null
+++ b/frappe/tests/ui/test_social_login_key_buttons.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+from __future__ import unicode_literals
+
+import unittest, frappe, time
+from frappe.utils.selenium_testdriver import TestDriver
+
+class TestSocialLoginKeyButtons(unittest.TestCase):
+ def setUp(self):
+ try:
+ frappe_login_key = frappe.get_doc("Social Login Key", "frappe")
+ except frappe.DoesNotExistError:
+ frappe_login_key = frappe.new_doc("Social Login Key")
+ frappe_login_key.get_social_login_provider("Frappe", initialize=True)
+ frappe_login_key.base_url = "http://localhost:8000"
+ frappe_login_key.enable_social_login = 1
+ frappe_login_key.client_id = "test_client_id"
+ frappe_login_key.client_secret = "test_client_secret"
+ frappe_login_key.save()
+
+ self.driver = TestDriver()
+
+ def test_login_buttons(self):
+
+ # Go to Login Page
+ self.driver.get("login")
+
+ time.sleep(2)
+ frappe_social_login = self.driver.find(".btn-frappe")
+ self.assertTrue(len(frappe_social_login) > 0)
+
+ def tearDown(self):
+ self.driver.close()
diff --git a/frappe/utils/html_utils.py b/frappe/utils/html_utils.py
index 3daa87d360..a45c85780b 100644
--- a/frappe/utils/html_utils.py
+++ b/frappe/utils/html_utils.py
@@ -1,4 +1,4 @@
-import json
+import json, re
import bleach, bleach_whitelist.bleach_whitelist as bleach_whitelist
from six import string_types
@@ -48,6 +48,24 @@ def is_json(text):
else:
return True
+def get_icon_html(icon, small=False):
+ emoji_pattern = re.compile(u'['
+ u'\U0001F300-\U0001F64F'
+ u'\U0001F680-\U0001F6FF'
+ u'\u2600-\u26FF\u2700-\u27BF]+',
+ re.UNICODE)
+
+ if icon and emoji_pattern.match(icon):
+ return '' + icon + ''
+
+ if frappe.utils.is_image(icon):
+ return \
+ ''.format(icon=icon) \
+ if small else \
+ '
'.format(icon=icon)
+ else:
+ return "".format(icon=icon)
+
# adapted from https://raw.githubusercontent.com/html5lib/html5lib-python/4aa79f113e7486c7ec5d15a6e1777bfe546d3259/html5lib/sanitizer.py
acceptable_elements = [
'a', 'abbr', 'acronym', 'address', 'area',
@@ -149,4 +167,4 @@ svg_attributes = [
'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type',
'xml:base', 'xml:lang', 'xml:space', 'xmlns', 'xmlns:xlink', 'y',
'y1', 'y2', 'zoomAndPan'
-]
\ No newline at end of file
+]
diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py
index 61498a9722..803aad2ac3 100644
--- a/frappe/utils/oauth.py
+++ b/frappe/utils/oauth.py
@@ -4,91 +4,36 @@
from __future__ import unicode_literals
import frappe
import frappe.utils
-import json
+import json, jwt
from frappe import _
+from frappe.utils.password import get_decrypted_password
from six import string_types
class SignupDisabledError(frappe.PermissionError): pass
def get_oauth2_providers():
- out = {
- "google": {
+ out = {}
+ providers = frappe.get_all("Social Login Key", fields=["*"])
+ for provider in providers:
+ authorize_url, access_token_url = provider.authorize_url, provider.access_token_url
+ if provider.custom_base_url:
+ authorize_url = provider.base_url + provider.authorize_url
+ access_token_url = provider.base_url + provider.access_token_url
+ out[provider.name] = {
"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",
- },
-
- "redirect_uri": "/api/method/frappe.www.login.login_via_google",
-
- "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.www.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.www.login.login_via_facebook",
-
- "auth_url_data": {
- "display": "page",
- "response_type": "code",
- "scope": "email,public_profile"
- },
-
- # relative to base_url
- "api_endpoint": "/v2.5/me",
- "api_endpoint_args": {
- "fields": "first_name,last_name,email,gender,location,verified,picture"
+ "name": provider.name,
+ "authorize_url": authorize_url,
+ "access_token_url": access_token_url,
+ "base_url": provider.base_url
},
+ "redirect_uri": provider.redirect_url,
+ "api_endpoint": provider.api_endpoint,
}
- }
+ if provider.auth_url_data:
+ out[provider.name]["auth_url_data"] = json.loads(provider.auth_url_data)
- frappe_server_url = frappe.db.get_value("Social Login Keys", None, "frappe_server_url")
- if frappe_server_url:
- out['frappe'] = {
- "flow_params": {
- "name": "frappe",
- "authorize_url": frappe_server_url + "/api/method/frappe.integrations.oauth2.authorize",
- "access_token_url": frappe_server_url + "/api/method/frappe.integrations.oauth2.get_token",
- "base_url": frappe_server_url
- },
-
- "redirect_uri": "/api/method/frappe.www.login.login_via_frappe",
-
- "auth_url_data": {
- "response_type": "code",
- "scope": "openid"
- },
-
- # relative to base_url
- "api_endpoint": "/api/method/frappe.integrations.oauth2.openid_profile"
- }
+ if provider.api_endpoint_args:
+ out[provider.name]["api_endpoint_args"] = json.loads(provider.api_endpoint_args)
return out
@@ -100,17 +45,13 @@ def get_oauth_keys(provider):
if not keys:
# try database
- social = frappe.get_doc("Social Login Keys", "Social Login Keys")
- keys = {}
- for fieldname in ("client_id", "client_secret"):
- value = social.get("{provider}_{fieldname}".format(provider=provider, fieldname=fieldname))
- if not value:
- keys = {}
- break
- keys[fieldname] = value
-
+ client_id, client_secret = frappe.get_value("Social Login Key", provider, ["client_id", "client_secret"])
+ client_secret = get_decrypted_password("Social Login Key", provider, "client_secret")
+ keys = {
+ "client_id": client_id,
+ "client_secret": client_secret
+ }
return keys
-
else:
return {
"client_id": keys["client_id"],
@@ -169,7 +110,11 @@ def login_via_oauth2(provider, code, state, decoder=None):
info = get_info_via_oauth(provider, code, decoder)
login_oauth_user(info, provider=provider, state=state)
-def get_info_via_oauth(provider, code, decoder=None):
+def login_via_oauth2_id_token(provider, code, state, decoder=None):
+ info = get_info_via_oauth(provider, code, decoder, id_token=True)
+ login_oauth_user(info, provider=provider, state=state)
+
+def get_info_via_oauth(provider, code, decoder=None, id_token=False):
flow = get_oauth2_flow(provider)
oauth2_providers = get_oauth2_providers()
@@ -186,9 +131,14 @@ def get_info_via_oauth(provider, code, decoder=None):
session = flow.get_auth_session(**args)
- api_endpoint = oauth2_providers[provider].get("api_endpoint")
- api_endpoint_args = oauth2_providers[provider].get("api_endpoint_args")
- info = session.get(api_endpoint, params=api_endpoint_args).json()
+ if id_token:
+ parsed_access = json.loads(session.access_token_response.text)
+ token = parsed_access['id_token']
+ info = jwt.decode(token, flow.client_secret, verify=False)
+ else:
+ api_endpoint = oauth2_providers[provider].get("api_endpoint")
+ api_endpoint_args = oauth2_providers[provider].get("api_endpoint_args")
+ info = session.get(api_endpoint, params=api_endpoint_args).json()
if (("verified_email" in info and not info.get("verified_email"))
or ("verified" in info and not info.get("verified"))):
@@ -289,26 +239,28 @@ def update_oauth_user(user, data, provider):
frappe.respond_as_web_page(_('Not Allowed'), _('User {0} is disabled').format(user.email))
return False
- if provider=="facebook" and not user.get("fb_userid"):
+ if provider=="facebook" and not user.get_social_login_userid(provider):
save = True
+ user.set_social_login_userid(provider, userid=data["id"], username=data.get("username"))
user.update({
- "fb_username": data.get("username"),
- "fb_userid": data["id"],
"user_image": "https://graph.facebook.com/{id}/picture".format(id=data["id"])
})
- elif provider=="google" and not user.get("google_userid"):
+ elif provider=="google" and not user.get_social_login_userid(provider):
save = True
- user.google_userid = data["id"]
+ user.set_social_login_userid(provider, userid=data["id"])
- elif provider=="github" and not user.get("github_userid"):
+ elif provider=="github" and not user.get_social_login_userid(provider):
save = True
- user.github_userid = data["id"]
- user.github_username = data["login"]
+ user.set_social_login_userid(provider, userid=data["id"], username=data.get("login"))
- elif provider=="frappe" and not user.get("frappe_userid"):
+ elif provider=="frappe" and not user.get_social_login_userid(provider):
save = True
- user.frappe_userid = data["sub"]
+ user.set_social_login_userid(provider, userid=data["sub"])
+
+ elif provider=="office_365" and not user.get_social_login_userid(provider):
+ save = True
+ user.set_social_login_userid(provider, userid=data["sub"])
if save:
user.flags.ignore_permissions = True
diff --git a/frappe/utils/password_strength.py b/frappe/utils/password_strength.py
index a6b8f3f2a9..2e781897ad 100644
--- a/frappe/utils/password_strength.py
+++ b/frappe/utils/password_strength.py
@@ -3,7 +3,11 @@
from __future__ import unicode_literals
-from zxcvbn import zxcvbn
+try:
+ from zxcvbn import zxcvbn
+except Exception as e:
+ import zxcvbn
+
import frappe
from frappe import _
diff --git a/frappe/www/login.html b/frappe/www/login.html
index 8cfbfea25b..cd85d3086f 100644
--- a/frappe/www/login.html
+++ b/frappe/www/login.html
@@ -39,27 +39,11 @@
- {%- if facebook_login is defined %}
-
- {{ _("Facebook") }}
- {%- endif -%}
-
- {%- if google_login is defined %}
-
- {{ _("Google") }}
- {%- endif -%}
-
- {%- if github_login is defined %}
-
- {{ _("GitHub") }}
- {%- endif -%}
-
- {%- if frappe_login is defined %}
-
-
{{ _("Frappe") }}
- {%- endif -%}
+ {% for provider in provider_logins %}
+
+ {{ provider.icon }} {{ provider.provider_name }}
+ {% endfor %}