Skip to content
Snippets Groups Projects
Commit 2b74657f authored by Nicolas Pernoud's avatar Nicolas Pernoud
Browse files

fix: Redirect is now with a code that is an opaque form of the token

parent de580395
No related branches found
No related tags found
No related merge requests found
......@@ -8,12 +8,14 @@
<script src="main.js"></script>
</head>
<body>
<h1>GLC Pro Demo Client</h1>
<h1>
GLC Pro Demo Client (Portail de service à partir duquel une démarche est
réalisée)
</h1>
<p>
Attention, pour le moment, les informations issues de GLC Pro sont
renvoyées directement à la place du code dans le callback, à des fins de
test. C'est une grave faille de sécurité, car cela laisse le client
modifier la donnée d'identité, sans contrôle du serveur.
récupérées par le client. C'est une grave faille de sécurité, car cela
laisse le client modifier la donnée d'identité, sans contrôle du serveur.
</p>
<button id="glcpro-auth">Connection avec GLC Pro</button>
<p id="id-content"></p>
......
......@@ -5,7 +5,48 @@ document.addEventListener("DOMContentLoaded", async () => {
});
const query = new URLSearchParams(window.location.search);
const code = query.get("code");
console.log(code);
// Exchange the code for an id token (not secure, for demo purposes ONLY, since client ID is not checked)
if (query != undefined && query != "") {
document.getElementById(`id-content`).innerHTML = code;
try {
const response = await fetch(
"http://localhost:8080/api/oidc/token?code=" +
encodeURIComponent(code) +
"&client_id=an_id&client_secret=a_secret&grant_type=authorization_code",
{
method: "GET",
}
);
if (response.status !== 200) {
throw new Error(`Could not get token (status ${response.status})`);
}
responseTxt = await response.text();
// Extract id
idToken = responseTxt.match(/id_token=([^&]*)/)[1];
idToken = b64DecodeUnicode(idToken);
idObj = JSON.parse(idToken);
document.getElementById(`id-content`).innerHTML = `Bienvenue ${
idObj.id_token.given_name
}, vous êtes habilité${
idObj.id_token.gender == "female" ? "e" : ""
} à agir pour le compte de l'entreprise ${
idObj.siren.entreprise.raison_sociale
}, votre jeton d'identité complet est :
<pre>${JSON.stringify(idObj, null, 2)}<pre>`;
} catch (e) {
console.error(e);
}
}
});
function b64DecodeUnicode(str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(
atob(str)
.split("")
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
}
......@@ -18,7 +18,7 @@ func CreateRootMux(staticDir string) *http.ServeMux {
fmt.Fprint(w, "OK")
})
mainMux.Handle("/api/", http.StripPrefix("/api", apiMux))
mainMux.Handle("/api/oidc/", http.StripPrefix("/api/oidc", oidcserver.CreateOIDCServer()))
mainMux.Handle("/api/oidc/", middlewares.Cors(http.StripPrefix("/api/oidc", oidcserver.CreateOIDCServer())))
// Serve static files falling back to serving index.html
mainMux.Handle("/", middlewares.NoCache(http.FileServer(&common.FallBackWrapper{Assets: http.Dir(staticDir)})))
// Put it together into the main handler
......
......@@ -9,6 +9,17 @@ import (
"strings"
)
// Cors enables CORS Request on server, CORS are accepted from the allowed domain, its parent domain, and all subdomains
func Cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PROPFIND, MKCOL, MOVE, COPY")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, XSRF-TOKEN, Authorization, Depth, Destination, X-OC-Mtime")
w.Header().Set("Access-Control-Allow-Credentials", "true")
next.ServeHTTP(w, req)
})
}
/*
**
Indirection layer to manage headers for WebSecurity middleware
......
package oidcserver
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
......@@ -21,6 +22,22 @@ type clientRequestData struct {
Sirent string
}
type GLCId struct {
Id FCIdentity `json:"id_token"`
E SirenResponse `json:"siren"`
}
type FCIdentity struct {
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Birthdate string `json:"birthdate"`
Gender string `json:"gender"`
Birthplace string `json:"birthplace"`
Birthcountry string `json:"birthcountry"`
PreferredUsername string `json:"preferred_username"`
Sub string `json:"sub"`
}
type SirenResponse struct {
Entreprise struct {
Siren string `json:"siren"`
......@@ -157,8 +174,9 @@ func CreateOIDCServer() *http.ServeMux {
http.Error(w, "could not perform the request to France Connect userinfo url", http.StatusInternalServerError)
return
}
rBody, _ = ioutil.ReadAll(resp.Body)
fmt.Printf("rBody: %v\n", string(rBody))
rBodyFC, _ := ioutil.ReadAll(resp.Body)
i := FCIdentity{}
json.Unmarshal(rBodyFC, &i)
// Get back the redirect url from the cookie
rd := clientRequestData{}
......@@ -186,21 +204,27 @@ func CreateOIDCServer() *http.ServeMux {
s := SirenResponse{}
json.Unmarshal(rBodyApi, &s)
// Match the siren to the requested siren
// Match the siren to the requested siren, if there is not match, redirect to the matcher, asking if a demand should be made
if s.Entreprise.Siren != rd.Sirent {
http.Redirect(w, req, "/matcher", http.StatusFound)
return
}
// TODO : redirect to the initial caller with an code that it actually the data bundled in an opaque token
///////////////////////////////////////////////////////////////////////////////////////////////////////
// TEMPORARY (FOR DEMO PURPOSES ONLY : SERIOUS SECURITY ISSUES) //
// Redirect to the initial client, with state and userinfo directly in place of authorisation code //
////////////////////////////////////////////////////////////////////////////////////////////////////
http.Redirect(w, req, rd.RedirectURI+"?code="+string(rBody)+string(rBodyApi), http.StatusFound)
// Redirect to the initial caller with an code that it actually the data bundled in an opaque token
tokenData := GLCId{
Id: i,
E: s,
}
code, err := tokens.Manager.CreateToken(tokenData, time.Now().Add(60*time.Second))
if err != nil {
http.Error(w, "could not create an authorisation code", http.StatusInternalServerError)
return
}
http.Redirect(w, req, rd.RedirectURI+"?code="+url.QueryEscape(code), http.StatusFound)
})
// Returns access token back to the user
// TODO : check if GET request is OK in OIDC protocol or replace with POST
mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
/// TODO : check that the client id is an actual client id
......@@ -224,23 +248,28 @@ func CreateOIDCServer() *http.ServeMux {
return
}
// Get and decode the autorisation code
/// TODO : tokenData will not stay a string but will be an id with company data
var tokenData string
_, err := tokens.Manager.ExtractAndValidateToken(r, "", &tokenData, false)
var id GLCId
_, err := tokens.Manager.ExtractAndValidateToken(r, "", &id, false)
if err != nil {
http.Error(w, "authorization code is invalid :"+err.Error(), http.StatusBadRequest)
http.Error(w, "authorization code is invalid: "+err.Error(), http.StatusBadRequest)
return
}
// Serialize the payload
sid, err := json.Marshal(id)
if err != nil {
http.Error(w, "could not serialize the "+err.Error(), http.StatusInternalServerError)
return
}
// TODO : Do a real JWT, instead of just base64
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
w.Write([]byte("id_token=" + tokenData + "&scope=user&token_type=id_token"))
w.Write([]byte("id_token=" + base64.StdEncoding.EncodeToString(sid) + "&scope=user&token_type=id_token"))
})
// Returns userinfo back to the user
/// TODO : implement
/// TODO : implement (if needed)
// Logout
/// TODO : Nothing : there is no persistent cookie at the time
/// TODO : Nothing : there is no persistent data cookie at the time
return mux
}
......@@ -13,7 +13,7 @@ class Auth {
document.getElementById(mountpoint).innerHTML = /* HTML */ `
<h1>Bienvenue sur GLC Pro</h1>
<p>Veuillez saisir le numéro de l'entreprise ou l'établissement pour lequel vous souhaitez faire une demande :</p>
<p>Pour ce POC, le seul SIREN accepté est celui de la Métropole de Lyon : 200046977 . Toute personne qui fait la demande avec ce SIREN sera habilité, toute autre SIREN donnera lieu à une proposition de délégation</p>
<p>Pour ce POC, le seul SIREN accepté est celui de la Métropole de Lyon : 200046977 . Toute personne qui fait la demande avec ce SIREN sera habilitée, toute autre SIREN donnera lieu à une proposition de délégation</p>
<p>Pour améliorer et avoir un comportement réaliste, il faudrait des comptes de test France Connect représentant des dirigeants dans l'API Entreprise</p>
<input type="text" id="auth-sirent"></input>
<button id="auth-do">OK</button>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment