Skip to content

Commit ce828b4

Browse files
committed
add OAuth example
1 parent a4568c4 commit ce828b4

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

examples/oauth-flask-ngrok.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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+
58+
# Get the ngrok public URI. It can be used instead of the localhost URI, as long
59+
# as it is also configured in the Webex integration
60+
r = requests.get("http://localhost:4040/api/tunnels")
61+
public_url = r.json()['tunnels'][0]['public_url']
62+
# OAUTH_CALLBACK_URI = public_url + "/callback"
63+
64+
# Create Flask app instance
65+
app = Flask(__name__)
66+
67+
# Flask secret key is required to use session
68+
app.secret_key = "very bad secret"
69+
70+
71+
# Welcome page. Link to auth from this page.
72+
@app.route("/")
73+
def root():
74+
print("/ requested")
75+
return ("""
76+
<p>Hey, this is Flask!</p>
77+
<p>Click <a href="{}">here</a> to authenticate Webex integration.</p>
78+
""".format(url_for("auth")))
79+
80+
81+
# OAuth Step 1 - Build authorization URL and redirect user.
82+
# Redirect the user/resource owner to the OAuth provider using an URL with a few
83+
# key OAuth parameters.
84+
@app.route("/auth")
85+
def auth():
86+
print("Authorization requested")
87+
88+
# State is used to prevent CSRF, generate random and save in session.
89+
# Not mandatory but improves security.
90+
oauth_state = str(uuid4())
91+
session['oauth_state'] = oauth_state
92+
93+
# All parameters we need to pass to authorization service
94+
oauth_params = {
95+
'response_type': "code",
96+
'client_id': OAUTH_CLIENT_ID,
97+
'redirect_uri': OAUTH_CALLBACK_URI,
98+
'scope': OAUTH_SCOPE,
99+
'state': oauth_state
100+
}
101+
authorizationUri = oauth_authorizationUri + urllib.parse.urlencode(oauth_params)
102+
103+
return redirect(authorizationUri)
104+
105+
106+
# OAuth Step 2 - User authorization.
107+
# This happens on the provider side.
108+
109+
110+
# OAuth Step 3 - Receive authoriation code and obtain access token.
111+
# The user has been redirected back from the provider to your registered
112+
# callback URL. With this redirection comes an authorization code included
113+
# in the redirect URL. We will use that code to obtain an access token.
114+
# The access token can be then used for any API calls within the authorized scopes.
115+
@app.route("/callback", methods=["GET"])
116+
def callback():
117+
print("OAuth callback received")
118+
119+
oauth_error = request.args.get("error_description", '')
120+
if oauth_error:
121+
return "OAuth error: " + oauth_error
122+
123+
oauth_code = request.args.get("code")
124+
if not oauth_code:
125+
return "OAuth error: Authorization provider did not return authorization code."
126+
127+
# check state to prevent CSRF
128+
oauth_state = request.args.get('state', '')
129+
if not oauth_state:
130+
return "OAuth error: Authorization provider did not return state."
131+
if oauth_state != session['oauth_state']:
132+
return "OAuth error: State does not match."
133+
134+
135+
# There are three options how the OAuth authorization code can be used
136+
137+
# 1.
138+
# The API connection can be directly initialized with OAuth information. It
139+
# will exchange the OAuth authorization code to access token behind the
140+
# scenes. It is the easiest, but the drawback is the refresh token is lost
141+
# and cannot be saved.
142+
api = WebexTeamsAPI(
143+
client_id=OAUTH_CLIENT_ID,
144+
client_secret=OAUTH_CLIENT_SECRET,
145+
oauth_code=oauth_code,
146+
redirect_uri=OAUTH_CALLBACK_URI
147+
)
148+
print(api.people.me())
149+
return "Welcome, {}!".format(api.people.me().displayName)
150+
151+
152+
# 2.
153+
# The API connection object can be initialized with any string. Then use the
154+
# access_tokens endpoint to obtain access and refresh tokens. The access
155+
# token is valid for 14 days. The refresh token may be saved somewhere and
156+
# later used to refresh the access token with access_tokens.refresh()
157+
# api = WebexTeamsAPI("any string")
158+
# access_tokens = api.access_tokens.get(
159+
# client_id=OAUTH_CLIENT_ID,
160+
# client_secret=OAUTH_CLIENT_SECRET,
161+
# code=oauth_code,
162+
# redirect_uri=OAUTH_CALLBACK_URI
163+
# )
164+
# access_token = access_tokens.access_token
165+
# refresh_token = access_tokens.refresh_token
166+
# # Reinit API connection with the obtained access_token
167+
# api.__init__(access_token)
168+
# print(api.people.me())
169+
# return "Welcome, {}!".format(api.people.me().displayName)
170+
171+
172+
# 3.
173+
# Alternatively, can use requests to exchange authorization code to access
174+
# token without usung the Webex SDK. Useful if your authorization process is
175+
# separate from the token usage. Save tokens somewhere for later use.
176+
# oauth_data = {
177+
# 'grant_type': "authorization_code",
178+
# 'redirect_uri': OAUTH_CALLBACK_URI,
179+
# 'code': oauth_code,
180+
# 'client_id': OAUTH_CLIENT_ID,
181+
# 'client_secret': OAUTH_CLIENT_SECRET
182+
# }
183+
# resp = requests.post(oauth_tokenUri, data=oauth_data)
184+
# if not resp.ok:
185+
# return "OAuth error: Authorization provider returned:<br>{}".format(resp.json()['error_description'])
186+
# else:
187+
# oauth_tokens = resp.json()
188+
# access_token = oauth_tokens["access_token"]
189+
# refresh_token = oauth_tokens["refresh_token"]
190+
# # save tokens
191+
# return "Authorization successful."
192+
193+
194+
if __name__ == "__main__":
195+
# Start Flask web server
196+
app.run(port=5000)

0 commit comments

Comments
 (0)