Skip to content

Commit c7cde7b

Browse files
author
Joseph Zanini
authored
Merge pull request #185 from zhenyamorozov/examples
examples: add OAuth example
2 parents 2896c8a + a09395c commit c7cde7b

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

examples/oauth-flask-ngrok.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""Implementation of OAuth for Webex Integration with Flask and ngrok.
4+
5+
This sample script leverages the Flask web service micro-framework (see
6+
https://flask.palletsprojects.com/).
7+
8+
Ngrok (https://ngrok.com/) can be used to tunnel traffic back to your server if
9+
your machine sits behind a firewall. Free account registration required. Ngrok
10+
is launched with "./ngrok http 5000"
11+
12+
You must create a Webex Integration in the My Webex Apps section of
13+
https://developer.webex.com. For details, see
14+
https://developer.webex.com/docs/integrations . Copy your integration Client ID,
15+
Client Secret, scopes and set Redirect URI as "http://localhost:5000/callback"
16+
or the public ngrok URI + "/callback".
17+
18+
Copyright (c) 2022 Cisco and/or its affiliates.
19+
20+
Permission is hereby granted, free of charge, to any person obtaining a copy of
21+
this software and associated documentation files (the "Software"), to deal in
22+
the Software without restriction, including without limitation the rights to
23+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
24+
the Software, and to permit persons to whom the Software is furnished to do so,
25+
subject to the following conditions:
26+
27+
The above copyright notice and this permission notice shall be included in all
28+
copies or substantial portions of the Software.
29+
30+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
32+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
33+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
34+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
35+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36+
"""
37+
38+
39+
from flask import Flask, url_for, session, redirect, request
40+
import urllib.parse
41+
from uuid import uuid4
42+
import requests
43+
44+
from webexteamssdk import WebexTeamsAPI
45+
46+
# Parameters configured in Webex Integration
47+
OAUTH_CLIENT_ID = "your integration Client ID"
48+
OAUTH_CLIENT_SECRET = "your integration Client Secret"
49+
OAUTH_CALLBACK_URI = "http://localhost:5000/callback"
50+
# Scopes are space-separated. Can use any subset of the configured scopes.
51+
OAUTH_SCOPE = "spark:people_read meeting:schedules_read"
52+
53+
# Static Webex URIs
54+
oauth_authorizationUri = "https://webexapis.com/v1/authorize?"
55+
oauth_tokenUri = "https://webexapis.com/v1/access_token"
56+
57+
# On a local machine, this script will run even without a public callback URI.
58+
# But in reality the user is never on the same computer with the server. To
59+
# enable remote user access, an Ngrok public URI has to be used instead of the
60+
# local URI.
61+
# Uncomment the lines below to automatically get the public URI from
62+
# ngrok and use it instead of the local one. This public URI must also be
63+
# configured in the Webex Integration as a Redirect URI.
64+
#
65+
# r = requests.get("http://localhost:4040/api/tunnels")
66+
# public_url = r.json()['tunnels'][0]['public_url']
67+
# OAUTH_CALLBACK_URI = public_url + "/callback"
68+
69+
# Create Flask app instance
70+
app = Flask(__name__)
71+
72+
# Flask secret key is required to use session
73+
app.secret_key = "very bad secret"
74+
75+
76+
# Welcome page. Link to auth from this page, or from anywhere else.
77+
@app.route("/")
78+
def root():
79+
print("/ requested")
80+
return ("""
81+
<p>Hey, this is Flask!</p>
82+
<p>Click <a href="{}">here</a> to authorize Webex Integration.</p>
83+
""".format(url_for("auth")))
84+
85+
86+
# OAuth Step 1 - Build authorization URL and redirect user.
87+
# Redirect the user/resource owner to the OAuth provider (Webex) using an URI
88+
# with a few key OAuth parameters.
89+
@app.route("/auth")
90+
def auth():
91+
print("Authorization requested")
92+
93+
# State is used to prevent CSRF, generate random and save in session.
94+
# Not mandatory but improves security.
95+
oauth_state = str(uuid4())
96+
session['oauth_state'] = oauth_state
97+
98+
# All parameters we need to pass to authorization service
99+
oauth_params = {
100+
'response_type': "code",
101+
'client_id': OAUTH_CLIENT_ID,
102+
'redirect_uri': OAUTH_CALLBACK_URI,
103+
'scope': OAUTH_SCOPE,
104+
'state': oauth_state
105+
}
106+
authorizationUri = oauth_authorizationUri + urllib.parse.urlencode(oauth_params)
107+
108+
return redirect(authorizationUri)
109+
110+
111+
# OAuth Step 2 - User authorization.
112+
# This happens on the provider side.
113+
114+
115+
# OAuth Step 3 - Receive authorization code and obtain access token.
116+
# The user has been redirected back from the provider to your registered
117+
# callback URI. With this request comes an authorization code included in the
118+
# redirect URI. We will use that code to obtain an access token. The access
119+
# token can be then used for any API calls within the authorized scopes.
120+
@app.route("/callback", methods=["GET"])
121+
def callback():
122+
print("OAuth callback received")
123+
124+
oauth_error = request.args.get("error_description", '')
125+
if oauth_error:
126+
return "OAuth error: " + oauth_error
127+
128+
oauth_code = request.args.get("code")
129+
if not oauth_code:
130+
return "OAuth error: Authorization provider did not return authorization code."
131+
132+
# check state to prevent CSRF
133+
oauth_state = request.args.get('state', '')
134+
if not oauth_state:
135+
return "OAuth error: Authorization provider did not return state."
136+
if oauth_state != session['oauth_state']:
137+
return "OAuth error: State does not match."
138+
139+
140+
# There are three options how the OAuth authorization code can be used
141+
142+
# 1.
143+
# The API connection can be directly initialized with OAuth information. It
144+
# will exchange the OAuth authorization code to an access token behind the
145+
# scenes. It is the easiest, but the drawback is the refresh token is lost
146+
# and cannot be saved.
147+
api = WebexTeamsAPI(
148+
client_id=OAUTH_CLIENT_ID,
149+
client_secret=OAUTH_CLIENT_SECRET,
150+
oauth_code=oauth_code,
151+
redirect_uri=OAUTH_CALLBACK_URI
152+
)
153+
print(api.people.me())
154+
return "Welcome, {}!".format(api.people.me().displayName)
155+
156+
157+
# 2.
158+
# The API connection object can be initialized with any string. Then use the
159+
# access_tokens endpoint to obtain access and refresh tokens. The access
160+
# token is valid for 14 days. The refresh token may be saved somewhere and
161+
# later used to refresh the access token with access_tokens.refresh()
162+
# api = WebexTeamsAPI("any string")
163+
# access_tokens = api.access_tokens.get(
164+
# client_id=OAUTH_CLIENT_ID,
165+
# client_secret=OAUTH_CLIENT_SECRET,
166+
# code=oauth_code,
167+
# redirect_uri=OAUTH_CALLBACK_URI
168+
# )
169+
# access_token = access_tokens.access_token
170+
# refresh_token = access_tokens.refresh_token
171+
# # Reinit API connection with the obtained access_token
172+
# api.__init__(access_token)
173+
# print(api.people.me())
174+
# return "Welcome, {}!".format(api.people.me().displayName)
175+
176+
177+
# 3.
178+
# Alternatively, can use requests to exchange authorization code to access
179+
# token without using the Webex SDK. Useful if your authorization process is
180+
# separate from the token usage. Save tokens somewhere for later use.
181+
# oauth_data = {
182+
# 'grant_type': "authorization_code",
183+
# 'redirect_uri': OAUTH_CALLBACK_URI,
184+
# 'code': oauth_code,
185+
# 'client_id': OAUTH_CLIENT_ID,
186+
# 'client_secret': OAUTH_CLIENT_SECRET
187+
# }
188+
# resp = requests.post(oauth_tokenUri, data=oauth_data)
189+
# if not resp.ok:
190+
# return "OAuth error: Authorization provider returned:<br>{}".format(resp.json()['error_description'])
191+
# else:
192+
# oauth_tokens = resp.json()
193+
# access_token = oauth_tokens["access_token"]
194+
# refresh_token = oauth_tokens["refresh_token"]
195+
# # save tokens
196+
# return "Authorization successful."
197+
198+
199+
if __name__ == "__main__":
200+
# Start Flask web server
201+
app.run(port=5000)

0 commit comments

Comments
 (0)