-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement RFC 8628 #3912
base: master
Are you sure you want to change the base?
feat: implement RFC 8628 #3912
Conversation
baaef03
to
469c5e1
Compare
469c5e1
to
7970287
Compare
7970287
to
8790af4
Compare
Instead of updating the device session, we were over-writing it causing existing session info that were created from fosite to be lost.
8790af4
to
14cf7cd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice! I'll push some minor changes from my side and left a couple of comments.
I primarily cleaned up error handling to make it standards compliant
consent/handler.go
Outdated
h.r.Writer().WriteError(w, r, errorsx.WithStack(fosite.ErrNotFound.WithWrap(err).WithHint(`'user_code' session not found`))) | ||
return | ||
} | ||
err = h.r.RFC8628HMACStrategy().ValidateUserCode(ctx, userCodeRequest, reqBody.UserCode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reqBody.UserCode is not actually being validated by validateusercode. We can probably remove it? Or should it be validated?
q := reqURL.Query() | ||
q.Add("client_id", userCodeRequest.GetClient().GetID()) | ||
reqURL.RawQuery = q.Encode() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we not always use the client from the usercode request? Why is it possible to override the client here? This is a bit confusing to me as to why we need this.
require.Contains(t, result.RedirectTo, "device_verifier") | ||
} | ||
|
||
func TestAcceptDuplicateDeviceRequest(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this testing if we can accept the same device challenge twice? If so, we need two tests:
- Test if we can accept the same device challenge twice and that the device verifier is valid
- Test if we can accept the device challenge once, use it, then try again, and fail
validateResponse: func(resp *http.Response) { | ||
require.EqualValues(t, http.StatusBadRequest, resp.StatusCode) | ||
}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you can add a test case here where a device auth code is already used
@@ -41,6 +41,7 @@ import ( | |||
) | |||
|
|||
const ( | |||
DeviceVerificationPath = "/oauth2/device/verify" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this URL coming from some spec?
// If there were multiple flows created for the same user_code then we may end up with multiple flow objects | ||
// persisted to the database, while only one of them was actually used to validate the user_code | ||
// (see https://github.com/ory/hydra/pull/3851#discussion_r1843678761) | ||
// TODO: We should wrap these queries in a transaction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO is open here
x.LogError(r, err, h.r.Logger()) | ||
h.r.Writer().WriteError(w, r, err) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? The audit logger is really not something we are using/maintaining. You can probably just log to the regular logger
if err != nil { | ||
h.r.Writer().WriteError(w, r, err) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change this to UPDATE ... SET accepted WHER nid=... AND request_id = ?
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I'm sorry, it appears that there are quite a lot of values being copied around here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know which values need to be updated here? Is it all of them? Or just some?
"prompt", | ||
"acr_values", | ||
"id_token_hint", | ||
"nonce", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks copy pasted - could you make this list a variable for re-use?
CREATE INDEX hydra_oauth2_device_auth_codes_request_id_idx ON hydra_oauth2_device_auth_codes (request_id, nid); | ||
CREATE INDEX hydra_oauth2_device_auth_codes_client_id_idx ON hydra_oauth2_device_auth_codes (client_id, nid); | ||
CREATE INDEX hydra_oauth2_device_auth_codes_challenge_id_idx ON hydra_oauth2_device_auth_codes (challenge_id); | ||
CREATE UNIQUE INDEX hydra_oauth2_device_auth_codes_user_code_signature_idx ON hydra_oauth2_device_auth_codes (user_code_signature, nid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only found queries for device_code_signature
(primary key) and request_id)
- do we need the challenge-id?
Also are we not missing a foreign key onto the oauth2 flow table (I'm not sure if we have that in the other tables)
Since you're referencing nid
as a foreign key, it will also need an index where it's the prefix.
This PR is a continuation of #3851. I have created it from my own personal repo and I have invited people from Ory to contribute so that we can speed up things. I think that most of the comments in the old PR were resolved, but I can copy them to this PR if we wish to keep the discussion history.
Implements the Device Authorization Grant to enable authentication for headless machines (see https://datatracker.ietf.org/doc/html/rfc8628)
Related issue(s)
Implements RFC 8628.
This PR is based on the work done on #3252, by @supercairos and @BuzzBumbleBee. That PR was based on an older version of Hydra and was missing some features/tests.
We have prepared a spec, that describes our design and implementation. We have tried to mimic the existing logic in Hydra and not make changes that would disrupt the existing workflows
Checklist
introduces a new feature.
contributing code guidelines.
vulnerability. If this pull request addresses a security vulnerability, I
confirm that I got the approval (please contact
[email protected]) from the maintainers to push
the changes.
works.
Further Comments
Notes:
memory
andpostgres
databases. The tests pass all of them.go.mod
.Testing
To test this you need to built the hydra image:
make docker
This will create an image with the name:
oryd/hydra:latest-sqlite
To run the flow you can use our UI, from https://github.com/canonical/identity-platform-login-ui/tree/hydra-device-test:
Create a client for Hydra:
docker exec -it identity-platform-login-ui-hydra-1 hydra create client --endpoint http://localhost:4445 --grant-type authorization_code,refresh_token,urn:ietf:params:oauth:grant-type:device_code --scope openid,offline_access,email,profile --token-endpoint-auth-method client_secret_post
Use that client to perform the device flow:
docker exec -it identity-platform-login-ui-hydra-1 hydra perform device-code --client-id <client-id> --client-secret <client-secret> -e http://localhost:4444 --scope openid,offline_access,email,profile
The user for logging in is:
[email protected]
test