forked from fastenhealth/fasten-onprem
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- make sure that we can generate offline tokens for Epic providers (u…
…sing dynamic client registration - https://fhir.epic.com/Documentation?docId=Oauth2§ion=Standalone-Oauth2-OfflineAccess-0) - when SourceCredential is sent to the CreateSource API, we'll check if dynamic client regirstion is enabled. If it is, we'll use this token to register a new client, store the client information, and geneate a new Access Token using the key pair associated with this newly registered client. - added additional fields to SourceCredential table (registration_endpoint, dynamic_client_registration_mode, dynamic_client_jwks, dynamic_client_id) - renamed RefreshTokens to SetTokens in SourceCredential model - to have consistent naming - added IsDynamicClient and RefreshDynamicClientAccessToken methods to SourceCredential Model fixes fastenhealth#178
- Loading branch information
Showing
12 changed files
with
445 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package jwk | ||
|
||
import ( | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/google/uuid" | ||
"github.com/lestrrat-go/jwx/v2/jwk" | ||
) | ||
|
||
//see https://github.com/lestrrat-go/jwx/blob/v2/docs/04-jwk.md#working-with-key-specific-methods | ||
func JWKGenerate() (jwk.RSAPrivateKey, error) { | ||
raw, err := rsa.GenerateKey(rand.Reader, 2048) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to generate RSA private key: %s\n", err) | ||
} | ||
|
||
key, err := jwk.FromRaw(raw) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create jwk.Key from RSA private key: %s\n", err) | ||
} | ||
|
||
rsakey, ok := key.(jwk.RSAPrivateKey) | ||
if !ok { | ||
return nil, fmt.Errorf("failed to convert jwk.Key into jwk.RSAPrivateKey (was %T)\n", key) | ||
} | ||
rsakey.Set("kid", uuid.New().String()) | ||
|
||
//_ = rsakey.D() | ||
//_ = rsakey.DP() | ||
//_ = rsakey.DQ() | ||
//_ = rsakey.E() | ||
//_ = rsakey.N() | ||
//_ = rsakey.P() | ||
//_ = rsakey.Q() | ||
//_ = rsakey.QI() | ||
//// OUTPUT: | ||
|
||
return rsakey, nil | ||
} | ||
|
||
func JWKSerialize(privateKey jwk.RSAPrivateKey) (map[string]string, error) { | ||
jsonbuf, err := json.Marshal(privateKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var dict map[string]string | ||
err = json.Unmarshal(jsonbuf, &dict) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if privateKey.KeyID() != "" { | ||
dict["kid"] = privateKey.KeyID() | ||
} | ||
|
||
return dict, err | ||
} | ||
|
||
func JWKDeserialize(privateKeyDict map[string]string) (jwk.RSAPrivateKey, error) { | ||
jsonbuf, err := json.Marshal(privateKeyDict) | ||
if err != nil { | ||
return nil, err | ||
} | ||
key, err := jwk.ParseKey(jsonbuf) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create jwk.Key from RSA private key: %s\n", err) | ||
} | ||
return key.(jwk.RSAPrivateKey), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package jwk | ||
|
||
import ( | ||
"github.com/lestrrat-go/jwx/v2/jwa" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func TestJWKSerialize(t *testing.T) { | ||
//test | ||
keypair, err := JWKGenerate() | ||
require.NoError(t, err) | ||
|
||
dict, err := JWKSerialize(keypair) | ||
require.NoError(t, err) | ||
|
||
keys := []string{} | ||
for key, _ := range dict { | ||
keys = append(keys, key) | ||
} | ||
|
||
//assert | ||
require.NotEmpty(t, keypair.KeyID()) | ||
require.ElementsMatch(t, []string{"d", "dp", "dq", "e", "kty", "n", "p", "q", "qi", "kid"}, keys) | ||
require.Equal(t, "RSA", dict["kty"]) | ||
//require.Equal(t, map[string]string{}, dict) | ||
|
||
require.Equal(t, string(keypair.N()), dict["n"]) | ||
} | ||
|
||
func TestJWKDeserialize(t *testing.T) { | ||
//setup | ||
keypairDict := map[string]string{ | ||
"kty": "RSA", | ||
"kid": "cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df", | ||
"n": "pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w", | ||
"e": "AQAB", | ||
"d": "ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q", | ||
"p": "4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0", | ||
"q": "ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8", | ||
"dp": "lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE", | ||
"dq": "mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk", | ||
"qi": "ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg", | ||
} | ||
|
||
//test | ||
keypair, err := JWKDeserialize(keypairDict) | ||
require.NoError(t, err) | ||
|
||
serialized, err := JWKSerialize(keypair) | ||
require.NoError(t, err) | ||
|
||
//assert | ||
require.NotEmpty(t, keypair.KeyID()) | ||
require.Equal(t, keypair.KeyID(), "cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df") | ||
require.Equal(t, keypair.KeyType(), jwa.KeyType("RSA")) | ||
|
||
require.Equal(t, keypairDict, serialized) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package models | ||
|
||
type ClientRegistrationRequest struct { | ||
SoftwareId string `json:"software_id"` | ||
Jwks ClientRegistrationRequestJwks `json:"jwks"` | ||
} | ||
|
||
type ClientRegistrationRequestJwks struct { | ||
Keys []ClientRegistrationRequestJwksKey `json:"keys"` | ||
} | ||
|
||
type ClientRegistrationRequestJwksKey struct { | ||
KeyId string `json:"kid"` | ||
KeyType string `json:"kty"` | ||
PublicExponent string `json:"e"` | ||
Modulus string `json:"n"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package models | ||
|
||
// ClientRegistrationResponse { | ||
// "redirect_uris": [ | ||
// " https://fhir.epic.com/test/smart" | ||
// ], | ||
// "token_endpoint_auth_method": "none", | ||
// "grant_types": [ | ||
// "urn:ietf:params:oauth:grant-type:jwt-bearer" | ||
// ], | ||
// "software_id": " d45049c3-3441-40ef-ab4d-b9cd86a17225", | ||
// "client_id": "G65DA2AF4-1C91-11EC-9280-0050568B7514", | ||
// "client_id_issued_at": 1632417134, | ||
// "jwks": { | ||
// "keys": [{ | ||
// "kty": "RSA", | ||
// "n": "vGASMnWdI-ManPgJi5XeT15Uf1tgpaNBmxfa-_bKG6G1DDTsYBy2K1uubppWMcl8Ff_2oWe6wKDMx2-bvrQQkR1zcV96yOgNmfDXuSSR1y7xk1Kd-uUhvmIKk81UvKbKOnPetnO1IftpEBm5Llzy-1dN3kkJqFabFSd3ujqi2ZGuvxfouZ-S3lpTU3O6zxNR6oZEbP2BwECoBORL5cOWOu_pYJvALf0njmamRQ2FKKCC-pf0LBtACU9tbPgHorD3iDdis1_cvk16i9a3HE2h4Hei4-nDQRXfVgXLzgr7GdJf1ArR1y65LVWvtuwNf7BaxVkEae1qKVLa2RUeg8imuw", | ||
// "e": "AQAB" | ||
// } | ||
// ] | ||
// } | ||
//} | ||
type ClientRegistrationResponse struct { | ||
RedirectUrls []string `json:"redirect_uris"` | ||
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"` | ||
GrantTypes []string `json:"grant_types"` | ||
SoftwareId string `json:"software_id"` | ||
ClientId string `json:"client_id"` | ||
ClientIdIssuedAt int `json:"client_id_issued_at"` | ||
Jwks ClientRegistrationRequestJwks `json:"jwks"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package models | ||
|
||
type ClientRegistrationTokenResponse struct { | ||
AccessToken string `json:"access_token"` | ||
TokenType string `json:"token_type"` | ||
ExpiresIn int `json:"expires_in"` | ||
Scope string `json:"scope"` | ||
State string `json:"state"` | ||
Patient string `json:"patient"` | ||
EpicDstu2Patient string `json:"__epic.dstu2.patient"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.