Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
Hiboo
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Terraform modules
Monitor
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
cyrinux
Hiboo
Commits
c09f16e5
Commit
c09f16e5
authored
5 years ago
by
kaiyou
Browse files
Options
Downloads
Patches
Plain Diff
Support the OIDC userinfo endpoint and simplify some of the oidc code
parent
2b9a3561
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
hiboo/sso/oidc.py
+74
-40
74 additions, 40 deletions
hiboo/sso/oidc.py
hiboo/utils.py
+16
-8
16 additions, 8 deletions
hiboo/utils.py
with
90 additions
and
48 deletions
hiboo/sso/oidc.py
+
74
−
40
View file @
c09f16e5
...
@@ -4,10 +4,10 @@ Supported grants are authorization code, OpenID implicit and hybrid.
...
@@ -4,10 +4,10 @@ Supported grants are authorization code, OpenID implicit and hybrid.
It relies heavily on authlib for the OAuth/OIDC implementation.
It relies heavily on authlib for the OAuth/OIDC implementation.
"""
"""
from
werkzeug.security
import
gen_salt
from
authlib.integrations
import
flask_oauth2
,
sqla_oauth2
from
authlib.integrations
import
flask_oauth2
,
sqla_oauth2
from
authlib.oauth2
import
rfc6749
as
oauth2
from
authlib.oauth2
import
rfc6749
as
oauth2
from
authlib.oidc
import
core
as
oidc
from
authlib.oidc
import
core
as
oidc
from
authlib.common
import
security
from
hiboo.sso
import
forms
,
blueprint
from
hiboo.sso
import
forms
,
blueprint
from
hiboo
import
models
,
utils
,
profile
from
hiboo
import
models
,
utils
,
profile
...
@@ -56,9 +56,9 @@ class Config(object):
...
@@ -56,9 +56,9 @@ class Config(object):
"""
"""
if
"
client_id
"
not
in
service
.
config
:
if
"
client_id
"
not
in
service
.
config
:
service
.
config
.
update
(
service
.
config
.
update
(
client_id
=
gen_salt
(
24
),
client_id
=
security
.
generate_token
(
24
),
client_secret
=
gen_salt
(
48
),
client_secret
=
security
.
generate_token
(
48
),
jwt_key
=
gen_salt
(
24
),
jwt_key
=
security
.
generate_token
(
24
),
jwt_alg
=
"
HS256
"
jwt_alg
=
"
HS256
"
)
)
...
@@ -72,7 +72,7 @@ class AuthorizationCodeMixin(object):
...
@@ -72,7 +72,7 @@ class AuthorizationCodeMixin(object):
def
create_authorization_code
(
self
,
client
,
grant_user
,
request
):
def
create_authorization_code
(
self
,
client
,
grant_user
,
request
):
obj
=
AuthorizationCodeMixin
.
AuthorizationCode
(
obj
=
AuthorizationCodeMixin
.
AuthorizationCode
(
code
=
gen_salt
(
48
),
nonce
=
request
.
data
.
get
(
"
nonce
"
)
or
""
,
code
=
security
.
generate_token
(
48
),
nonce
=
request
.
data
.
get
(
"
nonce
"
)
or
""
,
client_id
=
client
.
client_id
,
redirect_uri
=
request
.
redirect_uri
,
client_id
=
client
.
client_id
,
redirect_uri
=
request
.
redirect_uri
,
scope
=
request
.
scope
,
user_id
=
grant_user
.
uuid
,
scope
=
request
.
scope
,
user_id
=
grant_user
.
uuid
,
auth_time
=
int
(
time
.
time
())
auth_time
=
int
(
time
.
time
())
...
@@ -96,28 +96,23 @@ class AuthorizationCodeMixin(object):
...
@@ -96,28 +96,23 @@ class AuthorizationCodeMixin(object):
class
OpenIDMixin
(
object
):
class
OpenIDMixin
(
object
):
"""
Mixin for defining OpenID grants
"""
Mixin for defining OpenID grants, mostly a proxy to client methods,
either used as a grant extension for code grant, or as a direct mixin.
"""
"""
def
exists_nonce
(
self
,
nonce
,
request
):
def
exists_nonce
(
self
,
nonce
,
request
):
return
bool
(
utils
.
redis
.
get
(
"
nonce:{}
"
.
format
(
nonce
)))
return
bool
(
utils
.
redis
.
get
(
"
nonce:{}
"
.
format
(
nonce
)))
def
get_
jwt_config
(
self
,
grant
):
def
get_
client
(
self
,
grant
=
None
):
service
=
grant
.
client
.
service
# In the case of AuthorizationCode, the current object is not the grant
return
{
# but a grant extension, so the client is retrieved through the grant argument
'
key
'
:
service
.
config
[
"
jwt_key
"
],
'
alg
'
:
service
.
config
[
"
jwt_alg
"
],
return
self
.
request
.
client
if
grant
is
None
else
grant
.
client
'
iss
'
:
flask
.
url_for
(
"
sso.oidc_token
"
,
service_uuid
=
service
.
uuid
,
_external
=
True
),
'
exp
'
:
3600
,
def
get_jwt_config
(
self
,
grant
=
None
):
}
return
self
.
get_client
().
get_jwt_config
()
def
generate_user_info
(
self
,
user
,
scope
):
def
generate_user_info
(
self
,
user
,
scope
):
return
oidc
.
UserInfo
(
return
self
.
get_client
().
generate_user_info
(
user
,
scope
)
sub
=
user
.
uuid
,
name
=
user
.
username
,
prefered_username
=
user
.
username
,
login
=
user
.
username
,
email
=
user
.
email
)
class
Client
(
sqla_oauth2
.
OAuth2ClientMixin
):
class
Client
(
sqla_oauth2
.
OAuth2ClientMixin
):
...
@@ -140,42 +135,81 @@ class Client(sqla_oauth2.OAuth2ClientMixin):
...
@@ -140,42 +135,81 @@ class Client(sqla_oauth2.OAuth2ClientMixin):
# Configuration is stored in a format compatible with authlib metadata
# Configuration is stored in a format compatible with authlib metadata
# so it only needs to be passed to the authorization server object
# so it only needs to be passed to the authorization server object
self
.
client_metadata
=
service
.
config
self
.
client_metadata
=
service
.
config
self
.
authorization
=
flask_oauth2
.
AuthorizationServer
(
self
.
authorization
=
flask_oauth2
.
AuthorizationServer
(
query_client
=
self
.
query_client
,
save_token
=
self
.
save_token
)
query_client
=
self
.
query_client
,
self
.
authorization
.
generate_token
=
self
.
generate_token
save_token
=
self
.
save_token
,
# Register all grant types
app
=
flask
.
current_app
self
.
authorization
.
register_grant
(
Client
.
AuthorizationCodeGrant
,
[
Client
.
OpenIDCode
(
require_nonce
=
False
)])
)
self
.
authorization
.
register_grant
(
Client
.
AuthorizationCodeGrant
,
[
Client
.
OpenIDCode
(
require_nonce
=
False
)]
)
self
.
authorization
.
register_grant
(
Client
.
ImplicitGrant
)
self
.
authorization
.
register_grant
(
Client
.
ImplicitGrant
)
self
.
authorization
.
register_grant
(
Client
.
HybridGrant
)
self
.
authorization
.
register_grant
(
Client
.
HybridGrant
)
@classmethod
def
get_by_service
(
cls
,
service_uuid
):
service
=
models
.
Service
.
query
.
get
(
service_uuid
)
if
service
and
service
.
protocol
==
"
oidc
"
:
return
cls
(
service
)
def
query_client
(
self
,
client_id
):
def
query_client
(
self
,
client_id
):
return
self
if
client_id
==
self
.
client_id
else
None
return
self
if
client_id
==
self
.
client_id
else
None
def
get_jwt_config
(
self
):
service
=
self
.
service
return
{
'
key
'
:
service
.
config
[
"
jwt_key
"
],
'
alg
'
:
service
.
config
[
"
jwt_alg
"
],
'
iss
'
:
flask
.
url_for
(
"
sso.oidc_token
"
,
service_uuid
=
service
.
uuid
,
_external
=
True
),
'
exp
'
:
3600
,
}
def
generate_user_info
(
self
,
user
,
scope
):
"""
User info generation function used by the oidc code mixin and the userinfo endpoint
"""
return
oidc
.
UserInfo
(
sub
=
user
.
uuid
,
name
=
user
.
username
,
prefered_username
=
user
.
username
,
login
=
user
.
username
,
email
=
user
.
email
)
def
generate_token
(
self
,
client
,
grant_type
,
user
=
None
,
scope
=
None
,
expires_in
=
None
,
include_refresh_token
=
False
):
"""
Specific token generation function to help keep track of the profile associated with a token
"""
return
dict
(
client_id
=
self
.
client_id
,
token_type
=
"
Bearer
"
,
access_token
=
security
.
generate_token
(
48
),
issued_at
=
time
.
time
(),
expires_in
=
expires_in
or
3600
,
profile_uuid
=
user
.
uuid
,
scope
=
scope
or
""
)
def
save_token
(
self
,
token
,
request
):
def
save_token
(
self
,
token
,
request
):
# Tokens are not saved since Hiboo supports user authentication, note
"""
Save the token to redis database
# long term app authentication.
"""
pass
utils
.
redis
.
hmset
(
"
token:{}
"
.
format
(
token
[
"
access_token
"
]),
token
)
def
validate_token
(
self
,
request
):
"""
Validate then returns the current request token
"""
auth
=
request
.
headers
.
get
(
"
Authorization
"
,
""
).
split
(
None
,
1
)
if
auth
and
len
(
auth
)
==
2
and
auth
[
0
]
==
"
Bearer
"
:
token
=
utils
.
decode_dict
(
utils
.
redis
.
hgetall
(
"
token:{}
"
.
format
(
auth
[
1
])))
if
(
token
and
token
[
"
client_id
"
]
==
self
.
client_id
and
time
.
time
()
<
(
float
(
token
[
"
issued_at
"
])
+
float
(
token
[
"
expires_in
"
]))):
return
token
@blueprint.route
(
"
/oidc/authorize/<service_uuid>
"
,
methods
=
[
"
GET
"
,
"
POST
"
])
@blueprint.route
(
"
/oidc/authorize/<service_uuid>
"
,
methods
=
[
"
GET
"
,
"
POST
"
])
def
oidc_authorize
(
service_uuid
):
def
oidc_authorize
(
service_uuid
):
# Get the profile from user input (implies redirects)
# Get the profile from user input (implies redirects)
service
=
models
.
Service
.
query
.
get
(
service_uuid
)
or
flask
.
abort
(
404
)
client
=
Client
.
get_by_service
(
service_uuid
)
or
flask
.
abort
(
404
)
service
.
protocol
==
"
oidc
"
or
flask
.
abort
(
404
)
picked
=
profile
.
get_profile
(
client
.
service
,
intent
=
True
)
or
flask
.
abort
(
403
)
picked
=
profile
.
get_profile
(
service
,
intent
=
True
)
or
flask
.
abort
(
403
)
# Generate and return the response
# Generate and return the response
client
=
Client
(
service
)
return
client
.
authorization
.
create_authorization_response
(
grant_user
=
picked
)
return
client
.
authorization
.
create_authorization_response
(
grant_user
=
picked
)
@blueprint.route
(
"
/oidc/token/<service_uuid>
"
,
methods
=
[
"
POST
"
])
@blueprint.route
(
"
/oidc/token/<service_uuid>
"
,
methods
=
[
"
POST
"
])
def
oidc_token
(
service_uuid
):
def
oidc_token
(
service_uuid
):
# Get the profile from user input (implies redirects)
client
=
Client
.
get_by_service
(
service_uuid
)
or
flask
.
abort
(
404
)
service
=
models
.
Service
.
query
.
get
(
service_uuid
)
or
flask
.
abort
(
404
)
service
.
protocol
==
"
oidc
"
or
flask
.
abort
(
404
)
# Generate and return the response
client
=
Client
(
service
)
return
client
.
authorization
.
create_token_response
()
return
client
.
authorization
.
create_token_response
()
@blueprint.route
(
"
/oidc/userinfo/<service_uuid>
"
,
methods
=
[
"
GET
"
,
"
POST
"
])
def
oidc_userinfo
(
service_uuid
):
client
=
Client
.
get_by_service
(
service_uuid
)
or
flask
.
abort
(
404
)
token
=
client
.
validate_token
(
flask
.
request
)
profile
=
models
.
Profile
.
query
.
get
(
token
[
"
profile_uuid
"
])
return
client
.
generate_user_info
(
profile
,
token
[
"
scope
"
])
\ No newline at end of file
This diff is collapsed.
Click to expand it.
hiboo/utils.py
+
16
−
8
View file @
c09f16e5
...
@@ -77,6 +77,20 @@ def display_help(identifier):
...
@@ -77,6 +77,20 @@ def display_help(identifier):
return
result
return
result
def
encode_dict
(
source
,
valid_keys
=
None
):
return
{
key
.
encode
(
"
utf8
"
):
value
.
encode
(
"
utf8
"
)
if
type
(
value
)
is
str
else
value
for
key
,
value
in
source
.
items
()
if
(
valid_keys
is
None
or
key
in
valid_keys
)
}
def
decode_dict
(
source
):
return
{
key
.
decode
(
"
utf8
"
):
value
.
decode
(
"
utf8
"
)
if
type
(
value
)
is
bytes
else
value
for
key
,
value
in
source
.
items
()
}
class
SerializableObj
(
object
):
class
SerializableObj
(
object
):
def
__init__
(
self
,
**
kwargs
):
def
__init__
(
self
,
**
kwargs
):
self
.
__dict__
.
update
(
**
kwargs
)
self
.
__dict__
.
update
(
**
kwargs
)
...
@@ -84,16 +98,10 @@ class SerializableObj(object):
...
@@ -84,16 +98,10 @@ class SerializableObj(object):
@classmethod
@classmethod
def
unserialize
(
cls
,
kwargs
):
def
unserialize
(
cls
,
kwargs
):
return
cls
(
**
{
return
cls
(
**
decode_dict
(
kwargs
))
if
kwargs
else
None
key
.
decode
(
"
utf8
"
):
value
.
decode
(
"
utf8
"
)
if
type
(
value
)
is
bytes
else
value
for
key
,
value
in
kwargs
.
items
()
})
if
kwargs
else
None
def
serialize
(
self
):
def
serialize
(
self
):
return
{
return
encode_dict
(
self
.
__dict__
,
self
.
__keys__
)
key
.
encode
(
"
utf8
"
):
value
.
encode
(
"
utf8
"
)
if
type
(
value
)
is
str
else
value
for
key
,
value
in
self
.
__dict__
.
items
()
if
key
in
self
.
__keys__
}
# Request rate limitation
# Request rate limitation
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment