implement rp
This commit is contained in:
parent
b885398466
commit
75f503ce43
4 changed files with 153 additions and 28 deletions
|
@ -1,6 +1,8 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -188,18 +190,92 @@ func SignedJWTProfileAssertion(clientID string, audience []string, expiration ti
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceAuthorizationCaller interface {
|
type DeviceAuthorizationCaller interface {
|
||||||
GetDeviceCodeEndpoint() string
|
GetDeviceAuthorizationEndpoint() string
|
||||||
HttpClient() *http.Client
|
HttpClient() *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func CallDeviceAuthorizationEndpoint(request interface{}, caller DeviceAuthorizationCaller) (*oidc.DeviceAuthorizationResponse, error) {
|
func CallDeviceAuthorizationEndpoint(request *oidc.ClientCredentialsRequest, caller DeviceAuthorizationCaller) (*oidc.DeviceAuthorizationResponse, error) {
|
||||||
req, err := httphelper.FormRequest(caller.GetDeviceCodeEndpoint(), request, Encoder, nil)
|
req, err := httphelper.FormRequest(caller.GetDeviceAuthorizationEndpoint(), request, Encoder, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if request.ClientSecret != "" {
|
||||||
|
req.SetBasicAuth(request.ClientID, request.ClientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
resp := new(oidc.DeviceAuthorizationResponse)
|
resp := new(oidc.DeviceAuthorizationResponse)
|
||||||
if err := httphelper.HttpRequest(caller.HttpClient(), req, &resp); err != nil {
|
if err := httphelper.HttpRequest(caller.HttpClient(), req, &resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeviceAccessTokenRequest struct {
|
||||||
|
*oidc.ClientCredentialsRequest
|
||||||
|
oidc.DeviceAccessTokenRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallDeviceAccessTokenEndpoint(ctx context.Context, request *DeviceAccessTokenRequest, caller TokenEndpointCaller) (*oidc.AccessTokenResponse, error) {
|
||||||
|
req, err := httphelper.FormRequest(caller.TokenEndpoint(), request, Encoder, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if request.ClientSecret != "" {
|
||||||
|
req.SetBasicAuth(request.ClientID, request.ClientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpResp, err := caller.HttpClient().Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer httpResp.Body.Close()
|
||||||
|
|
||||||
|
resp := new(struct {
|
||||||
|
*oidc.AccessTokenResponse
|
||||||
|
*oidc.Error
|
||||||
|
})
|
||||||
|
if err = json.NewDecoder(httpResp.Body).Decode(resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if httpResp.StatusCode == http.StatusOK {
|
||||||
|
return resp.AccessTokenResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, resp.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func PollDeviceAccessTokenEndpoint(ctx context.Context, interval time.Duration, request *DeviceAccessTokenRequest, caller TokenEndpointCaller) (*oidc.AccessTokenResponse, error) {
|
||||||
|
for {
|
||||||
|
timer := time.After(interval)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-timer:
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, interval)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := CallDeviceAccessTokenEndpoint(ctx, request, caller)
|
||||||
|
if err == nil {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
interval += 5 * time.Second
|
||||||
|
}
|
||||||
|
var target *oidc.Error
|
||||||
|
if !errors.As(err, &target) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch target.ErrorType {
|
||||||
|
case oidc.AuthorizationPending:
|
||||||
|
continue
|
||||||
|
case oidc.SlowDown:
|
||||||
|
interval += 5 * time.Second
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,67 @@
|
||||||
package rp
|
package rp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/oidc/v2/pkg/client"
|
"github.com/zitadel/oidc/v2/pkg/client"
|
||||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DeviceAuthorization(clientID string, scopes []string, rp RelyingParty) (*oidc.DeviceAuthorizationResponse, error) {
|
func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.ClientCredentialsRequest, error) {
|
||||||
req := &oidc.DeviceAuthorizationRequest{
|
confg := rp.OAuthConfig()
|
||||||
Scopes: scopes,
|
req := &oidc.ClientCredentialsRequest{
|
||||||
ClientID: clientID,
|
GrantType: oidc.GrantTypeDeviceCode,
|
||||||
|
Scope: scopes,
|
||||||
|
ClientID: confg.ClientID,
|
||||||
|
ClientSecret: confg.ClientSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if signer := rp.Signer(); signer != nil {
|
||||||
|
assertion, err := client.SignedJWTProfileAssertion(rp.OAuthConfig().ClientID, []string{rp.Issuer()}, time.Hour, signer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build assertion: %w", err)
|
||||||
|
}
|
||||||
|
req.ClientAssertion = assertion
|
||||||
|
req.ClientAssertionType = oidc.ClientAssertionTypeJWTAssertion
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceAuthorization starts a new Device Authorization flow as defined
|
||||||
|
// in RFC 8628, section 3.1 and 3.2:
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc8628#section-3.1
|
||||||
|
func DeviceAuthorization(scopes []string, rp RelyingParty) (*oidc.DeviceAuthorizationResponse, error) {
|
||||||
|
req, err := newDeviceClientCredentialsRequest(scopes, rp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return client.CallDeviceAuthorizationEndpoint(req, rp)
|
return client.CallDeviceAuthorizationEndpoint(req, rp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// DeviceAccessToken attempts to obtain tokens from a Device Authorization,
|
||||||
func DeviceAccessToken() (*oauth2.Token, error) {
|
// by means of polling as defined in RFC, section 3.3 and 3.4:
|
||||||
req := &oidc.DeviceAccessTokenRequest{}
|
// https://www.rfc-editor.org/rfc/rfc8628#section-3.4
|
||||||
|
func DeviceAccessToken(ctx context.Context, deviceCode string, interval time.Duration, rp RelyingParty) (resp *oidc.AccessTokenResponse, err error) {
|
||||||
|
caller, ok := rp.(client.TokenEndpointCaller)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("rp does not implement TokenEndPointCaller")
|
||||||
|
}
|
||||||
|
req := &client.DeviceAccessTokenRequest{
|
||||||
|
DeviceAccessTokenRequest: oidc.DeviceAccessTokenRequest{
|
||||||
|
GrantType: oidc.GrantTypeDeviceCode,
|
||||||
|
DeviceCode: deviceCode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ClientCredentialsRequest, err = newDeviceClientCredentialsRequest(nil, rp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.PollDeviceAccessTokenEndpoint(ctx, interval, req, caller)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -59,7 +59,9 @@ type RelyingParty interface {
|
||||||
// UserinfoEndpoint returns the userinfo
|
// UserinfoEndpoint returns the userinfo
|
||||||
UserinfoEndpoint() string
|
UserinfoEndpoint() string
|
||||||
|
|
||||||
GetDeviceCodeEndpoint() string
|
// GetDeviceAuthorizationEndpoint returns the enpoint which can
|
||||||
|
// be used to start a DeviceAuthorization flow.
|
||||||
|
GetDeviceAuthorizationEndpoint() string
|
||||||
|
|
||||||
// IDTokenVerifier returns the verifier interface used for oidc id_token verification
|
// IDTokenVerifier returns the verifier interface used for oidc id_token verification
|
||||||
IDTokenVerifier() IDTokenVerifier
|
IDTokenVerifier() IDTokenVerifier
|
||||||
|
@ -123,8 +125,8 @@ func (rp *relyingParty) UserinfoEndpoint() string {
|
||||||
return rp.endpoints.UserinfoURL
|
return rp.endpoints.UserinfoURL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *relyingParty) GetDeviceCodeEndpoint() string {
|
func (rp *relyingParty) GetDeviceAuthorizationEndpoint() string {
|
||||||
return rp.endpoints.DeviceCodeURL
|
return rp.endpoints.DeviceAuthorizationURL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *relyingParty) GetEndSessionEndpoint() string {
|
func (rp *relyingParty) GetEndSessionEndpoint() string {
|
||||||
|
@ -506,7 +508,7 @@ type Endpoints struct {
|
||||||
JKWsURL string
|
JKWsURL string
|
||||||
EndSessionURL string
|
EndSessionURL string
|
||||||
RevokeURL string
|
RevokeURL string
|
||||||
DeviceCodeURL string
|
DeviceAuthorizationURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEndpoints(discoveryConfig *oidc.DiscoveryConfiguration) Endpoints {
|
func GetEndpoints(discoveryConfig *oidc.DiscoveryConfiguration) Endpoints {
|
||||||
|
@ -521,7 +523,7 @@ func GetEndpoints(discoveryConfig *oidc.DiscoveryConfiguration) Endpoints {
|
||||||
JKWsURL: discoveryConfig.JwksURI,
|
JKWsURL: discoveryConfig.JwksURI,
|
||||||
EndSessionURL: discoveryConfig.EndSessionEndpoint,
|
EndSessionURL: discoveryConfig.EndSessionEndpoint,
|
||||||
RevokeURL: discoveryConfig.RevocationEndpoint,
|
RevokeURL: discoveryConfig.RevocationEndpoint,
|
||||||
DeviceCodeURL: discoveryConfig.DeviceAuthorizationEndpoint,
|
DeviceAuthorizationURL: discoveryConfig.DeviceAuthorizationEndpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,6 @@ type DeviceAuthorizationResponse struct {
|
||||||
// https://www.rfc-editor.org/rfc/rfc8628#section-3.4,
|
// https://www.rfc-editor.org/rfc/rfc8628#section-3.4,
|
||||||
// Device Access Token Request.
|
// Device Access Token Request.
|
||||||
type DeviceAccessTokenRequest struct {
|
type DeviceAccessTokenRequest struct {
|
||||||
GrantType string `json:"grant_type"`
|
GrantType GrantType `json:"grant_type"`
|
||||||
DeviceCode string `json:"device_code"`
|
DeviceCode string `json:"device_code"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue