jcloude/press/api/oauth.py

207 lines
6.0 KiB
Python

import json
import os
import frappe
from frappe.core.utils import find
from frappe.utils import get_url
from frappe.utils.oauth import get_oauth2_authorize_url
from google.auth.transport.requests import Request
from google.oauth2 import id_token
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import Flow
from googleapiclient.discovery import build
from press.api.account import get_account_request_from_key, setup_account
from press.api.saas import (
check_subdomain_availability,
create_marketplace_subscription,
create_or_rename_saas_site,
)
from press.press.doctype.site.saas_site import get_saas_domain
from press.utils import log_error
from press.utils.telemetry import capture, identify
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
def google_oauth_flow():
config = frappe.conf.get("google_oauth_config")
redirect_uri = config["web"].get("redirect_uris")[0]
flow = Flow.from_client_config(
client_config=config,
scopes=[
"https://www.googleapis.com/auth/userinfo.profile",
"openid",
"https://www.googleapis.com/auth/userinfo.email",
],
redirect_uri=redirect_uri,
)
return flow
@frappe.whitelist(allow_guest=True)
def google_login(saas_app=None):
flow = google_oauth_flow()
authorization_url, state = flow.authorization_url()
minutes = 5
frappe.cache().set_value(
f"fc_oauth_state:{state}", saas_app or state, expires_in_sec=minutes * 60
)
return authorization_url
@frappe.whitelist(allow_guest=True)
def callback(code=None, state=None):
cached_key = f"fc_oauth_state:{state}"
cached_state = frappe.cache().get_value(cached_key)
saas_app = cached_state in frappe.db.get_all("Saas Settings", pluck="name")
frappe.cache().delete_value(cached_key)
if (state == cached_state) or (saas_app):
pass
else:
frappe.local.response["http_status_code"] = 401
return "Invalid state parameter. The session timed out. Please try again or contact Frappe Cloud support at https://jcloud.jingrow.com/support"
try:
flow = google_oauth_flow()
flow.fetch_token(authorization_response=frappe.request.url)
except Exception as e:
log_error("Google oauth Login failed", data=e)
frappe.local.response.type = "redirect"
frappe.local.response.location = "/dashboard/login"
# id_info
token_request = Request()
id_info = id_token.verify_oauth2_token(
id_token=flow.credentials._id_token,
request=token_request,
audience=frappe.conf.get("google_oauth_config")["web"]["client_id"],
)
email = id_info.get("email")
# phone (this may return nothing if info doesn't exists)
number = ""
if flow.credentials.refresh_token: # returns only for the first authorization
credentials = Credentials.from_authorized_user_info(
json.loads(flow.credentials.to_json())
)
service = build("people", "v1", credentials=credentials)
person = (
service.people().get(resourceName="people/me", personFields="phoneNumbers").execute()
)
if person:
phone = person.get("phoneNumbers")
if phone:
number = phone[0].get("value")
# saas signup
if saas_app and cached_state:
account_request = create_account_request(
email=email,
first_name=id_info.get("given_name"),
last_name=id_info.get("family_name"),
phone_number=number,
)
logo = frappe.db.get_value("Saas Signup Generator", cached_state, "image_path")
frappe.local.response.type = "redirect"
frappe.local.response.location = get_url(
f"/saas-oauth.html?app={cached_state}&key={account_request.request_key}&domain={get_saas_domain(cached_state)}&logo={logo}"
)
else:
# fc login or signup
if not frappe.db.exists("User", email):
account_request = create_account_request(
email=email,
first_name=id_info.get("given_name"),
last_name=id_info.get("family_name"),
phone_number=number,
)
frappe.local.response.type = "redirect"
frappe.local.response.location = (
f"/dashboard/setup-account/{account_request.request_key}"
)
# login
else:
frappe.local.login_manager.login_as(email)
frappe.local.response.type = "redirect"
frappe.response.location = "/dashboard"
def create_account_request(email, first_name, last_name, phone_number=""):
account_request = frappe.get_pg(
{
"doctype": "Account Request",
"team": email,
"email": email,
"first_name": first_name,
"last_name": last_name,
"phone_number": phone_number,
"send_email": False,
"role": "Press Admin",
"oauth_signup": True,
}
).insert(ignore_permissions=True)
frappe.db.commit()
return account_request
@frappe.whitelist(allow_guest=True)
def saas_setup(key, app, country, subdomain):
if not check_subdomain_availability(subdomain, app):
frappe.throw(f"Subdomain {subdomain} is already taken")
all_countries = frappe.db.get_all("Country", pluck="name")
country = find(all_countries, lambda x: x.lower() == country.lower())
if not country:
frappe.throw("Country filed should be a valid country name")
# create team and user
account_request = get_account_request_from_key(key)
if not frappe.db.exists("Team", {"user": account_request.email}):
setup_account(
key=key,
first_name=account_request.first_name,
last_name=account_request.last_name,
country=country,
oauth_signup=True,
)
# create a signup account request
signup_ar = frappe.get_pg(
{
"doctype": "Account Request",
"team": account_request.team,
"email": account_request.email,
"first_name": account_request.first_name,
"last_name": account_request.last_name,
"emaill": account_request.email,
"saas": True,
"erpnext": False,
"saas_app": app,
"role": "Press Admin",
"country": country,
"subdomain": subdomain,
}
).insert(ignore_permissions=True)
site_name = signup_ar.get_site_name()
identify(
site_name,
app=app,
oauth=True,
)
capture("completed_oauth_account_request", "fc_saas", site_name)
create_or_rename_saas_site(app, signup_ar)
frappe.set_user("Administrator")
create_marketplace_subscription(signup_ar)
return get_url("/prepare-site?key=" + signup_ar.request_key + "&app=" + app)
@frappe.whitelist(allow_guest=True)
def oauth_authorize_url(provider):
return get_oauth2_authorize_url(provider, None)