OAuth question - error with SSO


#1

Hi,
I’m using Pro 6.2.9 on Scientific Linux 7.

We recently installed a NetIQ IDM server that comes with an oauth service. It can be configured to authenticate other clients. It took me two days, because neither the netiq nor the seafile manual is clear about that, and my knowledge on oauth is limited, too :wink:
I had to change one entry in seafile’s oauth views.py file to get it to work, but I don’t know why.

This is my oauth configuration in seahub_settings.py:

ENABLE_OAUTH = "True"
OAUTH_ENABLE_INSECURE_TRANSPORT = True
OAUTH_CLIENT_ID = "seafile"
OAUTH_CLIENT_SECRET = "secret"
OAUTH_REDIRECT_URL = "https://seafile.server/oauth/callback/"
OAUTH_PROVIDER_DOMAIN = 'my.domain'
OAUTH_AUTHORIZATION_URL = 'https://idm.server/osp/a/idm/auth/oauth2/grant'
OAUTH_TOKEN_URL = 'https://idm.server/osp/a/idm/auth/oauth2/grant'
OAUTH_USER_INFO_URL = 'https://idm.server/osp/a/idm/auth/oauth2/getattributes?attributes=userCN'
OAUTH_SCOPE = ['',]
OAUTH_ATTRIBUTE_MAP = {
    "userCN": (True, "email"),
}

When I click on the “Single Sign-On” link in seafile’s login window, I’m redirected to the idm’s access login window. I enter the username “xmuster” and the password, and then I’m directed back to seafile, where the error ‘Error, please contact administrator.’ is displayed.

To find out what’s wrong I added two logger.info entries in seahub/seahub/oauth/views.py in function “format_user_info”:

    def format_user_info(user_info_resp):

    error = False
    user_info = {}
    user_info_json = user_info_resp.json()

    for item, attr in ATTRIBUTE_MAP.items():
        required, user_attr = attr
        value = user_info_json.get(item, '')

        logger.info('user_attr: %s = %s' % (user_attr, value))
        logger.info('required: %s' % required)
        if value:
            # ccnet email
            if user_attr == 'email':
                user_info[user_attr] = value if is_valid_email(str(value)) else \
                        '%s@%s' % (str(value), PROVIDER_DOMAIN)
            else:
                user_info[user_attr] = value
        elif required:
            error = True

    return user_info, error

Now, when I login, the seahub.log show the following errors:

 [INFO] seahub.oauth.views:134 format_user_info user_attr: email = xmuster
 [INFO] seahub.oauth.views:135 format_user_info required: True
 [INFO] seahub.oauth.views:134 format_user_info user_attr: email =
 [INFO] seahub.oauth.views:135 format_user_info required: True
 [ERROR] seahub.oauth.views:150 oauth_callback Required user info not found.
 [ERROR] seahub.oauth.views:151 oauth_callback {'email': 'xmuster@my.domain'}

Seafile finds the user “xmuster” and combines it with my provider domain to form the email address. What troubles me is that the function is called twice: the first time with the correct user info “email = xmuster”, and the second time with an empty value.

The error “Required user info not found” is caused by the statement

elif required:
        error = True

what always leads to an error if the “required” attribute is True. So I changed my seahub_settings to

OAUTH_ATTRIBUTE_MAP = {
    "userCN": (False, "email"),
}

But the error persists. There is still a second entry with the “required” attribute set to “True”:

 [INFO] seahub.oauth.views:134 format_user_info user_attr: email = xmuster
 [INFO] seahub.oauth.views:135 format_user_info required: False
 [INFO] seahub.oauth.views:134 format_user_info user_attr: email =
 [INFO] seahub.oauth.views:135 format_user_info required: True

Then I stumbled across the lines 38 ff. where the ATTRIBUTE_MAP is initialized:

 38     # Used for init an user for Seahub.
 39     PROVIDER_DOMAIN = getattr(settings, 'OAUTH_PROVIDER_DOMAIN', '')
 40     ATTRIBUTE_MAP = {
 41         'id': (True, "email"),
 42     }
 43     ATTRIBUTE_MAP.update(getattr(settings, 'OAUTH_ATTRIBUTE_MAP', {}))

This would explain why the function is called twice and why the “required” attribute is always “True”.

Now I simply commented out the “elif” statement and now my oauth sso works like a charm:

141             # elif required:
142             #     error = True

But the error should not occur. Maybe you should change the views.py or my configuration is wrong somewhere.


#2

Hello, first let me describe how we implement the process of getting user info from remote resource server functionality at the code level.

After you login to your OAuth provider successfully, Seafile will sent a request to OAUTH_USER_INFO_URL to get user info. Before it is inserted into Seafile database, we have to do some format work, which is the format_user_info function, that’s because of:

  1. Seafile only uses email to identify an unique user account in its system for now.

  2. The user info returned from different remote resource servers will be different, so we define a OAUTH_ATTRIBUTE_MAP setting to be an intermediary between Seafile and remote resource server.

If the remote resource server, like Google, uses email to identify an unique user too, Seafile will use Google’s email directorily, the OAUTH_ATTRIBUTE_MAP setting for Google should be like this:

OAUTH_ATTRIBUTE_MAP = {
    "email": (True, "email"),
    "name": (False, "name"),
}

First let us have a look at the value field (True, "email"): True stands for if email is required by Seafile, as I said “Seafile only uses email to identify an unique user account in its system for now.”, so the first item of OAUTH_ATTRIBUTE_MAP’s value field will always be True if its second item is email.

Then to the key field email of (True, "email"), this tells Seafile which attribute remote resoure server uses to indentify its user. Google uses email, so it’s "email" here.

For Github, who uses id to indenfy an unique user, so OAUTH_ATTRIBUTE_MAP for Github should be like this:

OAUTH_ATTRIBUTE_MAP = {
    "id": (True, "email"),
    "name": (False, "name"),
    "email": (False, "contact_email"),
}

After the id of an user is returned from Github, we combines it with OAUTH_PROVIDER_DOMAIN to be an email format string so it can be used by Seafile.


Now let us go to your problem.

I think this setting "userCN": (True, "email") is correct, I guess NetIQ IDM server uses userCN to identify an user, so True should be used here, not False.

So the key point maybe is here

Why it has two log info ?

Why the email is empty in the second log info. If the email (which is required) is empty, the value of error will be True, then the error ‘Error, please contact administrator.’ displayed.

One reason of having two log info is the format_user_info function is called twice, but there is another possible: the for loop for item, attr in ATTRIBUTE_MAP.items(): has been run twice.

Unfortunately, I can’t reproduce it on my test server, can you write some code to test it detailed?


#3

Hi Lian,

first of all, thank you for the comprehensive explanation.
I think the error lies in the initialization of the ATTRIBUTE_MAP variable in views.py. I logged the value of the variable during the login process and it has two values:

[INFO] seahub.oauth.views:44 <module> ATTRIBUTE_MAP = {'userCN': (True, 'email'), 'id': (True, 'email')}

The first value is from my seahub_settings and the second is initialized in views.py:

ATTRIBUTE_MAP = {
    'id': (True, "email"),
}
ATTRIBUTE_MAP.update(getattr(settings, 'OAUTH_ATTRIBUTE_MAP', {}))

I changed this to

#ATTRIBUTE_MAP = {
#    'id': (True, "email"),
#}
# ATTRIBUTE_MAP.update(getattr(settings, 'OAUTH_ATTRIBUTE_MAP', {}))
ATTRIBUTE_MAP = getattr(settings, 'OAUTH_ATTRIBUTE_MAP', {})

Now the attribute only has one value, as expected:

[INFO] seahub.oauth.views:45 <module> ATTRIBUTE_MAP = {'userCN': (True, 'email')}

and the oauth login works :slight_smile:


Oauth2 problems
#4

We had the same problem with our in-house KeyCloak server, no “id” field given in the user_info endpoint. Changing views.py based on your instructions fixed the prolem :+1:

@lian Could this fix be applied on Github?