TOTP via Passlib and PAS

Made some serious headway getting TOTP up and running with PAS but am stuck on the very last portion. I've got a python script using the scriptable plugin in PAS that auth's the user, and while the user/pass work just fine, calling the entered google authenticator token from the script after it has been entered into the web page is not working.

Here's the workflow:

  1. Go to login page and type in the username, password, and current auth token via the google authenticator app

  2. Submit the form and PAS goes to work auth'ing the user

The crazy part is that if I hard code the token variable in the pyton script to the current google auth token, and try to login fast enough so it doesn't change before I complete the login, it works just fine. However, calling it from the request namespace does not.

I have tried manually converting it to UTF-8, converting it to ascii, wrapping single quotes around it (just in case that wasn't happening), and virtually everything else I can think of. Nothing is working and I'm hoping you can help.

Here's the current script:

from ZcPassword import verify_password
from ZcPassword import totp_verify

login = credentials.get('login', None)
password = credentials.get('password', None)

#This is how I am trying to get the form variable intranet_token and though
#the auth fails, if I raise the exception, the error log shows me the 6 numbers I entered
mytoken = context.REQUEST.get('intranet_token', None)

# When I hard code the mytoken variable as described in the post it works
#mytoken = '272216'

#This exception returns the correct value of mytoken
#raise(Exception(mytoken))

if not login or not password or not mytoken or mytoken == "":
    raise(Exception(f"something is none, login: {login}, token: {mytoken}"))
    return (None, None)

for user in context.fetch_user_by_name(login=login):
    if not user.username == login:
        continue
    
    # Check password
    if not verify_password(password, user.password):
        return (None, None)

    # Check the totp
    if not totp_verify(mytoken, user.totp_key):
        return (None, None)

    return (user.id, user.username)

return (None, None)

I would of course look to the ZcPassword module or the login page itself, but as I said, if I hard code the token it properly logs in, which tells me everything is working as it should. The only thing I can think of is that I am getting the form variable in a format that is not just the 6 digits I entered, and instead the error_log is simply showing me a rendered output instead of what mytoken actually is.

As a follow up, here are a few things I've already tried:

If I change the line:

if not totp_verify(mytoken, user.totp_key):

To:

if not totp_verify('123456', user.totp_key):

Where '123456' is the correct token, it logs me in just fine.

I have also tried reformatting

mytoken = context.REQUEST.get('intranet_token', None)

To:

mytoken = str(context.REQUEST.get('intranet_token', None), 'utf-8')

And:

mytoken = repr(str(context.REQUEST.get('intranet_token', None), 'utf-8'))

Along with adding a second line of:

mytoken = context.REQUEST.get('intranet_token', None)
mytoken.encode('ascii', 'ignore')

If I enter 111111 for the token and raise(Exception(mytoken)) it shows me Exception:111111 in the error_log. If I enter the correct token I get unauthorized to access this resource.

Another followup. To ensure what I was getting was a string, it appears the only way to get a string is by calling the variable via:

mytoken = context.REQUEST.intranet_token

You can confirm this via:
print(isinstance(context.REQUEST.intranet_token, str))

That statement will return true. However, it still doesn't work to auth and log in.

FYI: I tried mytoken = repr(context.REQUEST.intranet_token) as well and it didn't work, but I re-verified that hard coding the value works just fine.

This means that the intranet_token is not passed in correctly.

Apparently, you have a way to determine the correct token. Compare the correct token to the value which arrives in your script. This should give an idea how to process the incoming value.

Thank you. I am aware that the token isn't being passed correctly. That's the reason I posted here :slight_smile:

That being said, I think I may know what's going on, but I do not know how to resolve it.

It has to be something to do with the way context.REQUEST is outputting the intranet_token. If I can hard code '123456' then I need a way to ensure that '123456' is exactly what context.REQUEST.intranet_token is giving me.

Any ideas?

Thus you compare the value you get with the one you expect.

request.<addr> is equivalent to request.get(<addr>) (provided <addr> is a request variable). Thus, you did not change anything.

Note that the values you find in request have been processed; they are intended to be used by programs and therefore are stripped of transport artefacts (such as encodings). The original value can be found in request.QUERY_STRING (for GET requests) and in request.BODY (for POST requests).
As your token values contain only ASCII letters, this should not be the cause of your problem.

The best way to advance is to compare the token value which arrives in your script to the value it should be.

1 Like

Plone Foundation Code of Conduct