grim/prosody_mod_auth_jetbrains_hub
Clone
Summary
Browse
Changes
Graph
overhaul all the things and add group checking support
2019-10-21, Gary Kramlich
297bf63a92af
overhaul all the things and add group checking support
-- Prosody IM
-- Copyright (C) 2008-2013 Matthew Wild
-- Copyright (C) 2008-2013 Waqas Hussain
-- Copyright (C) 2014 Kim Alvefur
-- Copyright (C) 2019 Gary Kramlich
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local
new_sasl
=
require
"util.sasl"
.
new
;
local
base64
=
require
"util.encodings"
.
base64
.
encode
;
local
json_decode
=
require
"util.json"
.
decode
local
have_async
,
async
=
pcall
(
require
,
"util.async"
);
local
log
=
module
.
_log
;
local
host
=
module
.
host
;
-- Options
local
hub_url
=
module
:
get_option_string
(
"jetbrains_hub_url"
,
""
):
gsub
(
"$host"
,
host
);
if
hub_url
==
""
then
error
(
"jetbrains_hub_url required"
)
end
local
hub_scopes
=
module
:
get_option_string
(
"jetbrains_hub_scopes"
,
""
);
if
hub_scopes
==
""
then
error
(
"jetbrains_hub_scopes required"
)
end
local
hub_client_id
=
module
:
get_option_string
(
"jetbrains_hub_client_id"
,
""
);
if
hub_client_id
==
""
then
error
(
"jetbrains_hub_client_id required"
)
end
local
hub_client_secret
=
module
:
get_option_string
(
"jetbrains_hub_client_secret"
,
""
);
if
hub_client_secret
==
""
then
error
(
"jetbrains_hub_client_secret required"
)
end
local
hub_groups
=
module
:
get_option_set
(
"jetbrains_hub_groups"
,
{});
-- we only need to update the group part of the query once on load so do it
-- right away
local
group_query
=
get_group_query
()
local
provider
=
{};
-- globals required by socket.http
if
rawget
(
_G
,
"PROXY"
)
==
nil
then
rawset
(
_G
,
"PROXY"
,
false
)
end
if
rawget
(
_G
,
"base_parsed"
)
==
nil
then
rawset
(
_G
,
"base_parsed"
,
false
)
end
local
http_request
=
nil
if
have_async
then
http_request
=
function
(
url
,
headers
,
body
)
local
http
=
require
"net.http"
;
local
wait
,
done
=
async
.
waiter
();
local
code
,
response
;
local
ex
=
{
headers
=
headers
;
body
=
http
.
formencode
(
body
);
}
local
function
cb
(
content_
,
code_
,
request_
,
response_
)
code
,
response
=
code_
,
response_
.
body
;
done
();
end
http
.
request
(
url
,
ex
,
cb
);
wait
();
return
{
code
=
code
;
content
=
response
;
}
end
else
prosody
.
unlock_globals
()
require
"ltn12"
require
"socket"
require
"socket.http"
require
"ssl.https"
prosody
.
lock_globals
()
http_request
=
function
(
url
,
headers
,
body
)
local
http
=
require
"socket.http"
;
local
https
=
require
"ssl.https"
;
local
request
;
if
string.sub
(
url
,
1
,
string.len
(
'https'
))
==
'https'
then
request
=
https
.
request
;
else
request
=
http
.
request
;
end
local
t
=
{}
local
_
,
code
,
_
=
request
{
url
=
url
,
headers
=
headers
,
body
=
http
.
formencode
(
body
),
sink
=
ltn12
.
sink
.
table
(
t
),
};
return
{
code
=
code
,
content
=
table.concat
(
t
),
}
end
end
local
function
get_group_query
()
-- if there's no groups, just return an empty string
if
#
hub_groups
==
0
then
return
""
end
-- we have at least one group, so output the start of our query
local
query
=
" and ("
for
i
,
v
in
ipairs
(
hub_groups
)
do
-- add the group making sure to quote it as spaces are allowed and will
-- fail if they're not quoted.
query
=
query
..
"in:
\"
"
..
v
..
"
\"
"
-- if we have additional groups add our "or" separator between the
-- groups.
if
i
<
#
hub_groups
then
query
=
query
..
" or "
end
end
-- finally add our closing paren and return the query
return
query
..
")"
end
local
function
check_group
(
username
,
token
)
local
headers
=
{
Authorization
=
"Bearer "
..
token
;
};
local
body
=
{
{
name
=
"$top"
,
value
=
"1"
};
{
name
=
"query"
,
value
=
"login:"
..
username
..
group_query
};
{
name
=
"fields"
,
value
=
"type"
};
}
local
resp
=
http_request
(
hub_url
..
"/api/rest/users"
,
headers
,
body
)
if
resp
[
"code"
]
==
200
then
local
json_payload
=
json_decode
(
response
.
body
);
if
json_payload
[
"total"
]
~=
1
then
return
nil
,
"Permission denied"
;
end
return
true
;
end
return
nil
,
"Failed to check access permissions"
end
function
provider
.
test_password
(
username
,
password
)
local
headers
=
{
Authorization
=
"Basic "
..
base64
(
hub_client_id
..
":"
..
hub_client_secret
);
};
local
body
=
{
{
name
=
"grant_type"
,
value
=
"password"
};
{
name
=
"username"
,
value
=
username
};
{
name
=
"password"
,
value
=
password
};
{
name
=
"scope"
,
value
=
hub_scopes
};
}
local
resp
=
http_request
(
hub_url
..
"/api/rest/oauth2/token"
,
headers
,
body
);
if
resp
[
"code"
]
>=
200
and
resp
[
"code"
]
<=
299
then
module
:
log
(
"debug"
,
"HTTP auth provider confirmed valid password"
);
if
#
hub_groups
>
0
then
local
json_payload
=
json_decode
(
resp
[
"content"
]);
return
async_group_check
(
username
,
json_payload
[
"access_token"
])
end
return
true
;
else
module
:
log
(
"debug"
,
"HTTP auth provider returned status code %d"
,
resp
[
"code"
]);
end
return
nil
,
"Auth failed. Invalid username or password."
;
end
function
provider
.
users
()
return
function
()
return
nil
;
end
end
function
provider
.
set_password
(
username
,
password
)
return
nil
,
"Changing passwords not supported"
;
end
function
provider
.
user_exists
(
username
)
return
true
;
end
function
provider
.
create_user
(
username
,
password
)
return
nil
,
"User creation not supported"
;
end
function
provider
.
delete_user
(
username
)
return
nil
,
"User deletion not supported"
;
end
function
provider
.
get_sasl_handler
()
return
new_sasl
(
host
,
{
plain_test
=
function
(
sasl
,
username
,
password
,
realm
)
return
provider
.
test_password
(
username
,
password
),
true
;
end
});
end
module
:
provides
(
"auth"
,
provider
);