Handle JWT tokens

This commit is contained in:
Filip Gralinski 2020-12-09 21:55:31 +01:00
parent a0577ba249
commit ceef7ae5ac
9 changed files with 1551 additions and 1 deletions

View File

@ -168,6 +168,9 @@ instance Yesod App where
isAuthorized (ChallengeHowToR _) _ = regularAuthorization isAuthorized (ChallengeHowToR _) _ = regularAuthorization
isAuthorized (ChallengeReadmeR _) _ = regularAuthorization isAuthorized (ChallengeReadmeR _) _ = regularAuthorization
isAuthorized (ChallengeAllSubmissionsR _) _ = regularAuthorization isAuthorized (ChallengeAllSubmissionsR _) _ = regularAuthorization
isAuthorized (ChallengeMySubmissionsJsonR _) _ = regularAuthorization
isAuthorized (ChallengeGraphDataR _) _ = regularAuthorization isAuthorized (ChallengeGraphDataR _) _ = regularAuthorization
isAuthorized (ChallengeDiscussionR _) _ = regularAuthorization isAuthorized (ChallengeDiscussionR _) _ = regularAuthorization
isAuthorized (ChallengeDiscussionFeedR _) _ = regularAuthorization isAuthorized (ChallengeDiscussionFeedR _) _ = regularAuthorization

View File

@ -20,6 +20,13 @@ import Handler.Dashboard
import Handler.Common import Handler.Common
import Handler.Evaluate import Handler.Evaluate
import qualified Data.ByteString as BS
import Data.Word8 (isSpace, toLower)
import Network.Wai (Request, requestHeaders)
import qualified Jose.Jwt as JWT
import qualified Jose.Jwa as JWA
import qualified Jose.Jwk as JWK
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import Text.Blaze import Text.Blaze
@ -649,6 +656,23 @@ submissionForm defaultUrl defBranch defaultGitAnnexRemote = renderBootstrap3 Boo
<*> areq textField (bfs MsgSubmissionBranch) defBranch <*> areq textField (bfs MsgSubmissionBranch) defBranch
<*> aopt textField (bfs MsgSubmissionGitAnnexRemote) (Just defaultGitAnnexRemote)) <*> aopt textField (bfs MsgSubmissionGitAnnexRemote) (Just defaultGitAnnexRemote))
getChallengeMySubmissionsJsonR :: Text -> Handler Value
getChallengeMySubmissionsJsonR name = do
req <- waiRequest
let mToken = case lookup "Authorization" (Network.Wai.requestHeaders req) of
Nothing -> Nothing
Just authHead -> case BS.break isSpace authHead of
(strategy, token)
| BS.map Data.Word8.toLower strategy == "bearer" -> (Just $ BS.filter (/= 32) token)
| otherwise -> Nothing
mUserEnt <- maybeAuth
app <- getYesod
let jwk = fromJust $ appJSONWebKey $ appSettings app
dtoken <- liftIO $ JWT.decode [jwk] (Just (JWT.JwsEncoding JWA.RS256)) $ fromJust mToken
return $ array [show dtoken]
getChallengeMySubmissionsR :: Text -> Handler Html getChallengeMySubmissionsR :: Text -> Handler Html
getChallengeMySubmissionsR name = do getChallengeMySubmissionsR name = do
userId <- requireAuthId userId <- requireAuthId

View File

@ -18,6 +18,9 @@ import Yesod.Default.Config2 (applyEnvValue, configSettingsYml)
import Yesod.Default.Util (WidgetFileSettings, widgetFileNoReload, import Yesod.Default.Util (WidgetFileSettings, widgetFileNoReload,
widgetFileReload) widgetFileReload)
import qualified Jose.Jwk as JWK
import Data.Aeson
data RepoScheme = SelfHosted | Branches data RepoScheme = SelfHosted | Branches
deriving (Eq, Show) deriving (Eq, Show)
@ -92,6 +95,7 @@ data AppSettings = AppSettings
, appServerSSHPublicKey :: Maybe Text , appServerSSHPublicKey :: Maybe Text
-- ^ Are challenges, submission, etc. visible without logging in -- ^ Are challenges, submission, etc. visible without logging in
, appIsPublic :: Bool , appIsPublic :: Bool
, appJSONWebKey :: Maybe JWK.Jwk
} }
instance FromJSON AppSettings where instance FromJSON AppSettings where
@ -137,6 +141,8 @@ instance FromJSON AppSettings where
appIsPublic <- o .:? "is-public" .!= False appIsPublic <- o .:? "is-public" .!= False
appJSONWebKey <- o .:? "json-web-key"
return AppSettings {..} return AppSettings {..}
-- | Settings for 'widgetFile', such as which template languages to support and -- | Settings for 'widgetFile', such as which template languages to support and

View File

@ -12,6 +12,7 @@
/list-challenges ListChallengesR GET /list-challenges ListChallengesR GET
/api/list-challenges ListChallengesJsonR GET /api/list-challenges ListChallengesJsonR GET
/api/leaderboard/#Text LeaderboardJsonR GET /api/leaderboard/#Text LeaderboardJsonR GET
/api/challenge-my-submissions/#Text ChallengeMySubmissionsJsonR GET
/list-archived-challenges ListArchivedChallengesR GET /list-archived-challenges ListArchivedChallengesR GET
/challenge-image/#ChallengeId ChallengeImageR GET /challenge-image/#ChallengeId ChallengeImageR GET

View File

@ -41,4 +41,25 @@ admin-user: "_env:ADMINUSER:"
admin-password: "_env:ADMINPASS:" admin-password: "_env:ADMINPASS:"
location: "_env:LOCATION:" location: "_env:LOCATION:"
# If set, the key given, in the JWK format, will be used to verify and
# trust JWT tokens sent by the client as Authorization/Bearer.
# The JWT token will be checked first for a given request, if not provided
# the standard auth procedure will used.
#
# If unset, the Authorization will NOT be checked at all (only the standard
# auth procedure will be applied).
#
# A JWK key is something like:
#
# {"kty":"RSA", alg:"RS256", "use":"sig", "kid":"h01jmt_bD-1Di8i_GYbEV2a4NxhptzySHO-R8VuNHVA", "e":"AQAB", "n": "qG1elE6KPW3BYMxNpgK73MoksvbrUSfpRY4z9hU5iMsJREyD5Ar6XpjM1xAr6G7xglnOoumPC9o6FqhDHihm6QdJ5s5MA9ZyGkbi--kvy9Qc2d_VIGU-UR4vwyk3hAwXOFLhoknpQrJBJmMQvGFdas1Yr-m9EIWwT1zN7neHZkRUYZSVyQw_XghtMIWAUsLnhr6mM7nstHLafgxe5Qamzuc4K5EC_qipFXu4ugYkMDnaknlhkT43m7tcduVDnv5GV_4dBesF7FRII8tgUQWyw3Ty_FIoq43SInUPU_9cxA-qPGQz5C50th2aJl1z1snpLWS_1Zfsa8lnFsMj8_oh6w"}
#
# If you use Keycloak, it can be retrived via:
#
# https://<HOST>/auth/realms/<REALM>/protocol/openid-connect/certs
#
# (key/0 element).
#
# Note: at the moment, only RS256 is handled.
json-web-key: "_env:JSON_WEB_KEY"
#analytics: UA-YOURCODE #analytics: UA-YOURCODE

View File

@ -152,6 +152,8 @@ library
, Glob , Glob
, req , req
, wai-cors , wai-cors
, word8
, jose-jwt
executable gonito executable gonito
if flag(library-only) if flag(library-only)

View File

@ -4,5 +4,18 @@ flags:
dev: false dev: false
packages: packages:
- '.' - '.'
extra-deps: [../geval,wai-handler-fastcgi-3.0.0.2,murmur3-1.0.3,random-strings-0.1.1.0,naturalcomp-0.0.3,Munkres-0.1,Chart-1.9.1,Chart-cairo-1.9.1,multiset-0.3.4.1,pwstore-fast-2.4.4,yesod-table-2.0.3,esqueleto-3.0.0,'ordered-containers-0.2.2@sha256:ebf2be3f592d9cf148ea6b8375f8af97148d44f82d8d04476899285e965afdbf,810'] extra-deps:
- ../geval
- wai-handler-fastcgi-3.0.0.2
- murmur3-1.0.3
- random-strings-0.1.1.0
- naturalcomp-0.0.3
- Munkres-0.1
- Chart-1.9.1
- Chart-cairo-1.9.1
- multiset-0.3.4.1
- pwstore-fast-2.4.4
- yesod-table-2.0.3
- esqueleto-3.0.0
- 'ordered-containers-0.2.2@sha256:ebf2be3f592d9cf148ea6b8375f8af97148d44f82d8d04476899285e965afdbf,810'
resolver: lts-12.26 resolver: lts-12.26

1426
static/js/keycloak.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
<html>
<head>
<script src="/static/js/keycloak.js"></script>
<script>
var keycloak;
function initKeycloak() {
keycloak = new Keycloak({
url: 'http://127.0.0.1:8080/auth',
realm: 'master',
clientId: 'myapp',
"enable-cors": true
})
keycloak.init({
onLoad: 'login-required'
}).then(function(authenticated) {
// alert(authenticated ? 'authenticated' : 'not authenticated');
}).catch(function() {
alert('failed to initialize');
});
}
var loadData = function () {
var url = 'http://127.0.0.1:3000/api/challenge-my-submissions/retroc2';
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.setRequestHeader('Accept', 'application/json');
req.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
alert(req.response);
} else if (req.status == 403) {
alert('Forbidden');
}
}
}
req.send();
};
</script>
</head>
<body onload="initKeycloak()">
<h1>This is a simple web page to test Gonito as a backend with authorization by JWT tokens.</h1>
<p><button onclick="loadData()">Test</button></p>
</body>
</html>