def get_login_url(request): if request.session.has_key( 'pkce_verifier') is False or request.session.has_key( 'code_challenge') is False: code_verifier = pkce.generate_code_verifier(length=128) code_challenge = pkce.get_code_challenge(code_verifier) request.session['pkce_verifier'] = code_verifier request.session['code_challenge'] = code_challenge redirect_url = request.build_absolute_uri(reverse("dashboard")) login_url = f"{settings.FUSION_AUTH_BASE_URL}/oauth2/authorize?client_id={settings.FUSION_AUTH_APP_ID}&redirect_uri={redirect_url}&response_type=code&code_challenge={request.session['code_challenge']}&code_challenge_method=S256" login_url = login_url.format( settings.FUSION_AUTH_BASE_URL, settings.FUSION_AUTH_APP_ID, ) return login_url
def get_youtube_auth_url(): state = generate_state() code_challenge = pkce.get_code_challenge(code_verifier) endpoint = "https://accounts.google.com/o/oauth2/v2/auth" payload = { "client_id": app_secrets.youtube_client_id, "redirect_uri": "http://127.0.0.1:5000", "response_type": "code", "scope": "https://www.googleapis.com/auth/youtube.readonly", "code_challenge": code_challenge, "code_challenge_method": "S256", "state": state, "access_type": "offline" } res = requests.head(endpoint, params=payload) print(res.url) return res.url
async def _oauth_authenticate(self) -> Tuple[str, int]: async with ClientSession() as session: # retrieve authentication page _LOGGER.debug("Retrieving authentication page") resp, html = await self.request( method="get", returns="text", url=OAUTH_AUTHORIZE_URI, websession=session, headers={ "redirect": "follow", }, params={ "client_id": OAUTH_CLIENT_ID, "code_challenge": get_code_challenge(self._code_verifier), "code_challenge_method": "S256", "redirect_uri": OAUTH_REDIRECT_URI, "response_type": "code", "scope": "MyQ_Residential offline_access", }, login_request=True, ) # Scanning returned web page for required fields. _LOGGER.debug("Scanning login page for fields to return") soup = BeautifulSoup(html, "html.parser") # Go through all potential forms in the page returned. This is in case multiple forms are returned. forms = soup.find_all("form") data = {} for form in forms: have_email = False have_password = False have_submit = False # Go through all the input fields. for field in form.find_all("input"): if field.get("type"): # Hidden value, include so we return back if field.get("type").lower() == "hidden": data.update({ field.get("name", "NONAME"): field.get("value", "NOVALUE") }) # Email field elif field.get("type").lower() == "email": data.update( {field.get("name", "Email"): self.username}) have_email = True # Password field elif field.get("type").lower() == "password": data.update({ field.get("name", "Password"): self.__credentials.get("password") }) have_password = True # To confirm this form also has a submit button elif field.get("type").lower() == "submit": have_submit = True # Confirm we found email, password, and submit in the form to be submitted if have_email and have_password and have_submit: break # If we're here then this is not the form to submit. data = {} # If data is empty then we did not find the valid form and are unable to continue. if len(data) == 0: _LOGGER.debug("Form with required fields not found") raise RequestError( "Form containing fields for email, password and submit not found." "Unable to continue login process.") # Perform login to MyQ _LOGGER.debug("Performing login to MyQ") resp, _ = await self.request( method="post", returns="response", url=resp.url, websession=session, headers={ "Content-Type": "application/x-www-form-urlencoded", "Cookie": resp.cookies.output(attrs=[]), }, data=data, allow_redirects=False, login_request=True, ) # We're supposed to receive back at least 2 cookies. If not then authentication failed. if len(resp.cookies) < 2: message = "Invalid MyQ credentials provided. Please recheck login and password." self._invalid_credentials = True _LOGGER.debug(message) raise InvalidCredentialsError(message) # Intercept redirect back to MyQ iOS app _LOGGER.debug("Calling redirect page") resp, _ = await self.request( method="get", returns="response", url=f"{OAUTH_BASE_URI}{resp.headers['Location']}", websession=session, headers={ "Cookie": resp.cookies.output(attrs=[]), }, allow_redirects=False, login_request=True, ) # Retrieve token _LOGGER.debug("Getting token") redirect_url = f"{OAUTH_BASE_URI}{resp.headers['Location']}" resp, data = await self.request( returns="json", method="post", url=OAUTH_TOKEN_URI, websession=session, headers={ "Content-Type": "application/x-www-form-urlencoded", }, data={ "client_id": OAUTH_CLIENT_ID, "client_secret": OAUTH_CLIENT_SECRET, "code": parse_qs(urlsplit(redirect_url).query).get("code", ""), "code_verifier": self._code_verifier, "grant_type": "authorization_code", "redirect_uri": OAUTH_REDIRECT_URI, "scope": parse_qs(urlsplit(redirect_url).query).get( "code", "MyQ_Residential offline_access"), }, login_request=True, ) token = f"{data.get('token_type')} {data.get('access_token')}" try: expires = int(data.get("expires_in", DEFAULT_TOKEN_REFRESH)) except ValueError: _LOGGER.debug( f"Expires {data.get('expires_in')} received is not an integer, using default." ) expires = DEFAULT_TOKEN_REFRESH * 2 if expires < DEFAULT_TOKEN_REFRESH * 2: _LOGGER.debug( f"Expires {expires} is less then default {DEFAULT_TOKEN_REFRESH}, setting to default instead." ) expires = DEFAULT_TOKEN_REFRESH * 2 return token, expires
def test_get_code_challenge_too_short_or_too_long(length): verifier = ''.join(random.choices(string.ascii_letters, k=length)) with pytest.raises(ValueError): pkce.get_code_challenge(verifier)
def test_get_code_challenge(): abc = 'abcdefghijklmnopqrstuvwxyz' challenge = pkce.get_code_challenge(abc + abc.upper()) assert challenge == 'OWQpS2ZGE3mNGkd-uK0CEYtI0MVzjEJ2EyAvLtEjtfE'
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.client_id = credentials.create_client_id() self.client_secret = credentials.create_client_secret() self.code_verifier = credentials.create_code_verifier() self.code_challenge = pkce.get_code_challenge(self.code_verifier)
AUTH0_CALLBACK_URL = env.get(constants.AUTH0_CALLBACK_URL) AUTH0_CLIENT_ID = env.get(constants.AUTH0_CLIENT_ID) AUTH0_CLIENT_SECRET = env.get(constants.AUTH0_CLIENT_SECRET) AUTH0_DOMAIN = env.get(constants.AUTH0_DOMAIN) AUTH0_BASE_URL = 'https://' + AUTH0_DOMAIN AUTH0_AUDIENCE = env.get(constants.AUTH0_AUDIENCE) MY_URL = env.get('MY_URL') app = Flask(__name__, static_url_path='/public', static_folder='./public') app.secret_key = constants.SECRET_KEY app.debug = True code_verifier = pkce.generate_code_verifier(length=128) code_challenge = pkce.get_code_challenge(code_verifier) @app.errorhandler(Exception) def handle_auth_error(ex): response = jsonify(message=str(ex)) response.status_code = (ex.code if isinstance(ex, HTTPException) else 500) return response oauth = OAuth(app) auth0 = oauth.register( 'auth0', client_id=AUTH0_CLIENT_ID, client_secret=AUTH0_CLIENT_SECRET, api_base_url=AUTH0_BASE_URL + '/',