ErikBoesen / schoolopy

:school: Python wrapper for Schoology's REST API.
MIT License
47 stars 19 forks source link

Reuse user's oauth token #24

Closed gadhagod closed 3 years ago

gadhagod commented 3 years ago

Hello. Once a user logs in using 3-legged, I get to see their Schoology stuff, like in the three-legged example. Cool.

However, is there a way to access their data even after the session is over? According to the docs:

OAuth allows your application to access the Schoology API without needing the user's login credentials. If this is the first time that a user has accessed your application, you conduct an OAuth handshake to obtain a set of OAuth tokens for that user. You can save these tokens in your database. The next time the user accesses your application, you can retrieve the tokens and access the API without having to go through the OAuth handshake process again.

But how can I re-access their data from schoolopy? Using the Auth command? What should I store in a database? Please let me know if I need to clarify this issue. Thanks.

goombamaui commented 3 years ago

If one was able to use the same token to keep getting data from the server, it poses a big security risk. If anyone were to get hands on that token, they would be able to constantly get data from the application. The only way to make more tokens would be to use the username and password. The best way to constantly get access to information would be to store the username and password in a secure place (password vaults) and then whenever the token expires, use these in order to get a new token.

gadhagod commented 3 years ago

But when using 3-legged authentication I don't get the user's username and password.

ErikBoesen commented 3 years ago

You'll definitely want to store the token, not a username and password. Tokens are meant for maintenance of long-term connections that can be revoked or manipulated. Storing user credentials would be quite dangerous.

That said, it's been a while since I created this project, and I didn't implement 3-legged auth, so I'm not totally sure how to answer your question. @AndrewLester may have an answer as he was the one that added 3-legged auth.

AndrewLester commented 3 years ago

If you'd like to reuse a user's authentication state after a session ends, you'll need to save their access_token and access_token_secret. When I implemented three-legged oauth, I didn't make it obvious how to do this. You can access the variables that hold their tokens, once they've been authorized, from the Auth instance, which is available from the main Schoology instance under schoology_auth.

Then, you can apply these tokens to a newly created Auth instance through the access_token and access_token_secret parameters on the constructor.

Replacing the current structure of the Auth class with something like Authlib would be a good thing to do.

gadhagod commented 3 years ago

Thanks @AndrewLester. Will try this tonight.

goombamaui commented 3 years ago

I tried this code by putting the tokens into the auth. However, the new auth created using the token and token_secret has the wrong user.

import schoolopy
from os import getenv
from flask import Flask, redirect

auth = schoolopy.Auth(getenv('SCHOOLOGY_KEY'), getenv('SCHOOLOGY_SECRET'), three_legged=True, domain='https://schoology.harker.org/')
url = auth.request_authorization(callback_url="localhost"+'/authorized')
app=Flask(__name__)
@app.route("/login")
def login():
    return redirect(url)

@app.route("/authorized")
def authorized():
    print(auth.authorize())
    tok=auth.access_token
    sec=auth.access_token_secret
    auth2 = schoolopy.Auth(getenv('SCHOOLOGY_KEY'), getenv('SCHOOLOGY_SECRET'), three_legged=False, domain='https://schoology.harker.org/', request_token=tok, request_token_secret=sec)
    print(auth2.authorize())
    sc=schoolopy.Schoology(auth)
    sc2=schoolopy.Schoology(auth2)
    print(sc.get_me().username)
    #user1
    print(sc2.get_me().username)
    #owner of api
    return(".")

app.run(host="0.0.0.0",port=80)

Shouldn't the sc.get_me().username be same as the sc2.get_me().username?

AndrewLester commented 3 years ago
    auth2 = schoolopy.Auth(getenv('SCHOOLOGY_KEY'), getenv('SCHOOLOGY_SECRET'), three_legged=False, domain='https://schoology.harker.org/', request_token=tok, request_token_secret=sec)

Try using the access_token and access_token_secret keyword arguments instead of request_token and request_token_secret here.

Other than that, your code looks good.

Edit: You actually don't even have to call authorize() on the auth2 instance.

Edit 2: Also, you'll want to set three_legged to True on the auth2 instance because it's still using the three_legged oauth protocol. Ok thats all the edits I think, sorry about that.

goombamaui commented 3 years ago

The following didn't work either: I set three_legged to True and changed request_token to access_token and request_token_secret to access_token_secret.

import schoolopy
from os import getenv
from flask import Flask, redirect

auth = schoolopy.Auth(getenv('SCHOOLOGY_KEY'), getenv('SCHOOLOGY_SECRET'), three_legged=True, domain='https://schoology.harker.org/')
url = auth.request_authorization(callback_url="localhost"+'/authorized')
app=Flask(__name__)
@app.route("/login")
def login():
    return redirect(url)

@app.route("/authorized")
def authorized():
    auth.authorize()
    tok=auth.access_token
    sec=auth.access_token_secret
    auth2 = schoolopy.Auth(getenv('SCHOOLOGY_KEY'), getenv('SCHOOLOGY_SECRET'), three_legged=True, domain='https://schoology.harker.org/', access_token=tok, access_token_secret=sec)
    sc=schoolopy.Schoology(auth)
    sc2=schoolopy.Schoology(auth2)
    print(sc.get_me().username)
    #user1
    print(sc2.get_me().username)
    #owner of api
    return(".")

app.run(host="0.0.0.0",port=80)

I still get the owner of the api for sc2.

AndrewLester commented 3 years ago

That is understandable. I probably should have tested my solution before sending it.

Here's my updated solution:

auth2 = schoolopy.Auth(getenv('SCHOOLOGY_KEY'), getenv('SCHOOLOGY_SECRET'), three_legged=True, domain='https://schoology.harker.org/', access_token=tok, access_token_secret=sec)
auth2.oauth.token = {'oauth_token': tok, 'oauth_token_secret': sec}

Just add that second line. In all honesty, this is pretty much just a hack to bypass the jankyness of the Auth class.

For the future of three-legged OAuth in schoolopy, I think using another OAuth library like Authlib inside the Auth class would be more beneficial, as requests-oauthlib definitely isn't the most intuitive.

goombamaui commented 3 years ago

Thank you so much, this solution fixed the problem. Here is the final code incase anyone else wants to reference it:

import schoolopy
from os import getenv
from flask import Flask, redirect

auth = schoolopy.Auth(getenv('SCHOOLOGY_KEY'), getenv('SCHOOLOGY_SECRET'), three_legged=True, domain='https://schoology.harker.org/')
url = auth.request_authorization(callback_url="localhost"+'/authorized')
app=Flask(__name__)
@app.route("/login")
def login():
    return redirect(url)

@app.route("/authorized")
def authorized():
    auth.authorize()
    tok=auth.access_token
    sec=auth.access_token_secret
    auth2 = schoolopy.Auth(getenv('SCHOOLOGY_KEY'), getenv('SCHOOLOGY_SECRET'), three_legged=True, domain='https://schoology.harker.org/', access_token=tok, access_token_secret=sec)
    auth2.oauth.token = {'oauth_token': tok, 'oauth_token_secret': sec}
    sc=schoolopy.Schoology(auth)
    sc2=schoolopy.Schoology(auth2)
    print(sc.get_me().username)
    #user1
    print(sc2.get_me().username)
    #same as user 1
    return(".")

app.run(host="0.0.0.0",port=80)
gadhagod commented 3 years ago

Yup. We got it working. Thanks @AndrewLester.