From 5119d7aea308f974c24c99fbbeaded36db78187f Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Thu, 29 Apr 2021 09:20:01 +0200 Subject: [PATCH] begin refresh token --- example/internal/mock/storage.go | 6 + go.sum | 54 ++++ pkg/client/rp/relaying_party.go | 1 - pkg/oidc/token_request.go | 36 +++ pkg/op/{authrequest.go => auth_request.go} | 3 +- ...threquest_test.go => auth_request_test.go} | 0 pkg/op/storage.go | 2 + pkg/op/token.go | 87 ++++--- pkg/op/token_code.go | 107 ++++++++ pkg/op/token_exchange.go | 30 +++ pkg/op/token_jwt_profile.go | 79 ++++++ pkg/op/token_refresh.go | 113 ++++++++ pkg/op/token_request.go | 121 +++++++++ pkg/op/tokenrequest.go | 242 ------------------ pkg/op/verifier_jwt_profile.go | 5 + 15 files changed, 611 insertions(+), 275 deletions(-) rename pkg/op/{authrequest.go => auth_request.go} (98%) rename pkg/op/{authrequest_test.go => auth_request_test.go} (100%) create mode 100644 pkg/op/token_code.go create mode 100644 pkg/op/token_exchange.go create mode 100644 pkg/op/token_jwt_profile.go create mode 100644 pkg/op/token_refresh.go create mode 100644 pkg/op/token_request.go delete mode 100644 pkg/op/tokenrequest.go diff --git a/example/internal/mock/storage.go b/example/internal/mock/storage.go index d00d7a5..247459e 100644 --- a/example/internal/mock/storage.go +++ b/example/internal/mock/storage.go @@ -154,6 +154,12 @@ func (s *AuthStorage) AuthRequestByID(_ context.Context, id string) (op.AuthRequ func (s *AuthStorage) CreateToken(_ context.Context, authReq op.TokenRequest) (string, time.Time, error) { return "id", time.Now().UTC().Add(5 * time.Minute), nil } +func (s *AuthStorage) AuthRequestByRefreshToken(_ context.Context, token string) (op.AuthRequest, error) { + if token != c { + return nil, errors.New("invalid token") + } + return a, nil +} func (s *AuthStorage) TerminateSession(_ context.Context, userID, clientID string) error { return nil } diff --git a/go.sum b/go.sum index 371d837..bb48d24 100644 --- a/go.sum +++ b/go.sum @@ -12,50 +12,70 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo= github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -81,6 +101,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -95,7 +116,9 @@ github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6C github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -103,11 +126,14 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -118,14 +144,21 @@ github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlI github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -133,12 +166,15 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -146,11 +182,13 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32 h1:5tjfNdR2ki3yYQ842+eX2sQHeiwpKJ0RnHO4IYOc4V8= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -167,8 +205,10 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -179,14 +219,17 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -230,6 +273,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -269,6 +313,7 @@ golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -309,6 +354,7 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -330,6 +376,7 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -366,6 +413,7 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -378,6 +426,7 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -394,6 +443,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -408,7 +458,11 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/client/rp/relaying_party.go b/pkg/client/rp/relaying_party.go index 528f554..5a48951 100644 --- a/pkg/client/rp/relaying_party.go +++ b/pkg/client/rp/relaying_party.go @@ -165,7 +165,6 @@ func NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, sco return nil, err } } - endpoints, err := Discover(rp.issuer, rp.httpClient) if err != nil { return nil, err diff --git a/pkg/oidc/token_request.go b/pkg/oidc/token_request.go index 0c5b70b..1136f8e 100644 --- a/pkg/oidc/token_request.go +++ b/pkg/oidc/token_request.go @@ -10,6 +10,9 @@ const ( //GrantTypeCode defines the grant_type `authorization_code` used for the Token Request in the Authorization Code Flow GrantTypeCode GrantType = "authorization_code" + //GrantTypeCode defines the grant_type `refresh_token` used for the Token Request in the Refresh Token Flow + GrantTypeRefreshToken GrantType = "refresh_token" + //GrantTypeBearer defines the grant_type `urn:ietf:params:oauth:grant-type:jwt-bearer` used for the JWT Authorization Grant GrantTypeBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer" @@ -44,6 +47,39 @@ func (a *AccessTokenRequest) GrantType() GrantType { return GrantTypeCode } +//SetClientID implements op.AuthenticatedTokenRequest +func (a *AccessTokenRequest) SetClientID(clientID string) { + a.ClientID = clientID +} + +//SetClientSecret implements op.AuthenticatedTokenRequest +func (a *AccessTokenRequest) SetClientSecret(clientSecret string) { + a.ClientSecret = clientSecret +} + +type RefreshTokenRequest struct { + RefreshToken string `schema:"refresh_token"` + Scopes Scopes `schema:"scope"` + ClientID string `schema:"client_id"` + ClientSecret string `schema:"client_secret"` + ClientAssertion string `schema:"client_assertion"` + ClientAssertionType string `schema:"client_assertion_type"` +} + +func (a *RefreshTokenRequest) GrantType() GrantType { + return GrantTypeRefreshToken +} + +//SetClientID implements op.AuthenticatedTokenRequest +func (a *RefreshTokenRequest) SetClientID(clientID string) { + a.ClientID = clientID +} + +//SetClientSecret implements op.AuthenticatedTokenRequest +func (a *RefreshTokenRequest) SetClientSecret(clientSecret string) { + a.ClientSecret = clientSecret +} + type JWTTokenRequest struct { Issuer string `json:"iss"` Subject string `json:"sub"` diff --git a/pkg/op/authrequest.go b/pkg/op/auth_request.go similarity index 98% rename from pkg/op/authrequest.go rename to pkg/op/auth_request.go index 3a79b9b..dcb5e39 100644 --- a/pkg/op/authrequest.go +++ b/pkg/op/auth_request.go @@ -89,7 +89,7 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) { RedirectToLogin(req.GetID(), client, w, r) } -//ParseAuthorizeRequest parsed the http request into a AuthRequest +//ParseAuthorizeRequest parsed the http request into a oidc.AuthRequest func ParseAuthorizeRequest(r *http.Request, decoder utils.Decoder) (*oidc.AuthRequest, error) { err := r.ParseForm() if err != nil { @@ -289,6 +289,7 @@ func CreateAuthRequestCode(ctx context.Context, authReq AuthRequest, storage Sto return code, nil } +//BuildAuthRequestCode builds the string representation of the auth code func BuildAuthRequestCode(authReq AuthRequest, crypto Crypto) (string, error) { return crypto.Encrypt(authReq.GetID()) } diff --git a/pkg/op/authrequest_test.go b/pkg/op/auth_request_test.go similarity index 100% rename from pkg/op/authrequest_test.go rename to pkg/op/auth_request_test.go diff --git a/pkg/op/storage.go b/pkg/op/storage.go index 277f244..adba5cf 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -17,6 +17,8 @@ type AuthStorage interface { DeleteAuthRequest(context.Context, string) error CreateToken(context.Context, TokenRequest) (string, time.Time, error) + CreateTokens(ctx context.Context, request TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) + RefreshTokenRequestByRefreshToken(context.Context, string) (RefreshTokenRequest, error) TerminateSession(context.Context, string, string) error diff --git a/pkg/op/token.go b/pkg/op/token.go index 334bec9..8592003 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -21,53 +21,61 @@ type TokenRequest interface { GetScopes() []string } -func CreateTokenResponse(ctx context.Context, authReq AuthRequest, client Client, creator TokenCreator, createAccessToken bool, code string) (*oidc.AccessTokenResponse, error) { - var accessToken string +func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Client, creator TokenCreator, createAccessToken bool, code string) (*oidc.AccessTokenResponse, error) { + var accessToken, refreshToken string var validity time.Duration if createAccessToken { var err error - accessToken, validity, err = CreateAccessToken(ctx, authReq, client.AccessTokenType(), creator, client) + accessToken, refreshToken, validity, err = CreateAccessToken(ctx, request, client.AccessTokenType(), creator, client) if err != nil { return nil, err } } - idToken, err := CreateIDToken(ctx, creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client) + idToken, err := CreateIDToken(ctx, creator.Issuer(), request, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client) if err != nil { return nil, err } - err = creator.Storage().DeleteAuthRequest(ctx, authReq.GetID()) - if err != nil { - return nil, err + if authRequest, ok := request.(AuthRequest); ok { + err = creator.Storage().DeleteAuthRequest(ctx, authRequest.GetID()) + if err != nil { + return nil, err + } } exp := uint64(validity.Seconds()) return &oidc.AccessTokenResponse{ - AccessToken: accessToken, - IDToken: idToken, - TokenType: oidc.BearerToken, - ExpiresIn: exp, + AccessToken: accessToken, + IDToken: idToken, + RefreshToken: refreshToken, + TokenType: oidc.BearerToken, + ExpiresIn: exp, }, nil } -func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator) (*oidc.AccessTokenResponse, error) { - accessToken, validity, err := CreateAccessToken(ctx, tokenRequest, AccessTokenTypeBearer, creator, nil) - if err != nil { - return nil, err +func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storage) (id, refreshToken string, exp time.Time, err error) { + if needsRefreshToken(tokenRequest) { + id, exp, err = storage.CreateToken(ctx, tokenRequest) + return } - - exp := uint64(validity.Seconds()) - return &oidc.AccessTokenResponse{ - AccessToken: accessToken, - TokenType: oidc.BearerToken, - ExpiresIn: exp, - }, nil + return storage.CreateTokens(ctx, tokenRequest, "hodor") } -func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client Client) (token string, validity time.Duration, err error) { - id, exp, err := creator.Storage().CreateToken(ctx, tokenRequest) +func needsRefreshToken(tokenRequest TokenRequest) bool { + switch req := tokenRequest.(type) { + case AuthRequest: + return utils.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode + case RefreshTokenRequest: + return true + default: + return false + } +} + +func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client Client) (accessToken, refreshToken string, validity time.Duration, err error) { + id, refreshToken, exp, err := createTokens(ctx, tokenRequest, creator.Storage()) if err != nil { - return "", 0, err + return "", "", 0, err } var clockSkew time.Duration if client != nil { @@ -75,10 +83,10 @@ func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTok } validity = exp.Add(clockSkew).Sub(time.Now().UTC()) if accessTokenType == AccessTokenTypeJWT { - token, err = CreateJWT(ctx, creator.Issuer(), tokenRequest, exp, id, creator.Signer(), client, creator.Storage()) + accessToken, err = CreateJWT(ctx, creator.Issuer(), tokenRequest, exp, id, creator.Signer(), client, creator.Storage()) return } - token, err = CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto()) + accessToken, err = CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto()) return } @@ -99,10 +107,27 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex return utils.Sign(claims, signer.Signer()) } -func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, client Client) (string, error) { +type IDTokenRequest interface { + //GetACR() string + //GetAMR() []string + GetAudience() []string + GetAuthTime() time.Time + GetClientID() string + GetScopes() []string + GetSubject() string +} + +func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, client Client) (string, error) { exp := time.Now().UTC().Add(client.ClockSkew()).Add(validity) - claims := oidc.NewIDTokenClaims(issuer, authReq.GetSubject(), authReq.GetAudience(), exp, authReq.GetAuthTime(), authReq.GetNonce(), authReq.GetACR(), authReq.GetAMR(), authReq.GetClientID(), client.ClockSkew()) - scopes := client.RestrictAdditionalIdTokenScopes()(authReq.GetScopes()) + var acr, nonce string + var amr []string + if authRequest, ok := request.(AuthRequest); ok { + acr = authRequest.GetACR() + amr = authRequest.GetAMR() + nonce = authRequest.GetNonce() + } + claims := oidc.NewIDTokenClaims(issuer, request.GetSubject(), request.GetAudience(), exp, request.GetAuthTime(), nonce, acr, amr, request.GetClientID(), client.ClockSkew()) + scopes := client.RestrictAdditionalIdTokenScopes()(request.GetScopes()) if accessToken != "" { atHash, err := oidc.ClaimHash(accessToken, signer.SignatureAlgorithm()) if err != nil { @@ -115,7 +140,7 @@ func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, vali } if len(scopes) > 0 { userInfo := oidc.NewUserInfo() - err := storage.SetUserinfoFromScopes(ctx, userInfo, authReq.GetSubject(), authReq.GetClientID(), scopes) + err := storage.SetUserinfoFromScopes(ctx, userInfo, request.GetSubject(), request.GetClientID(), scopes) if err != nil { return "", err } diff --git a/pkg/op/token_code.go b/pkg/op/token_code.go new file mode 100644 index 0000000..0f27104 --- /dev/null +++ b/pkg/op/token_code.go @@ -0,0 +1,107 @@ +package op + +import ( + "context" + "errors" + "net/http" + + "github.com/caos/oidc/pkg/oidc" + "github.com/caos/oidc/pkg/utils" +) + +//CodeExchange handles the OAuth 2.0 authorization_code grant, including +//parsing, validating, authorizing the client and finally exchanging the code for tokens +func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + tokenReq, err := ParseAccessTokenRequest(r, exchanger.Decoder()) + if err != nil { + RequestError(w, r, err) + } + if tokenReq.Code == "" { + RequestError(w, r, ErrInvalidRequest("code missing")) + return + } + authReq, client, err := ValidateAccessTokenRequest(r.Context(), tokenReq, exchanger) + if err != nil { + RequestError(w, r, err) + return + } + resp, err := CreateTokenResponse(r.Context(), authReq, client, exchanger, true, tokenReq.Code) + if err != nil { + RequestError(w, r, err) + return + } + utils.MarshalJSON(w, resp) +} + +//ParseAccessTokenRequest parsed the http request into a oidc.AccessTokenRequest +func ParseAccessTokenRequest(r *http.Request, decoder utils.Decoder) (*oidc.AccessTokenRequest, error) { + request := new(oidc.AccessTokenRequest) + err := ParseAuthenticatedTokenRequest(r, decoder, request) + if err != nil { + return nil, err + } + return request, nil +} + +//ValidateAccessTokenRequest validates the token request parameters including authorization check of the client +//and returns the previous created auth request corresponding to the auth code +func ValidateAccessTokenRequest(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exchanger Exchanger) (AuthRequest, Client, error) { + authReq, client, err := AuthorizeCodeClient(ctx, tokenReq, exchanger) + if err != nil { + return nil, nil, err + } + if client.GetID() != authReq.GetClientID() { + return nil, nil, ErrInvalidRequest("invalid auth code") + } + if tokenReq.RedirectURI != authReq.GetRedirectURI() { + return nil, nil, ErrInvalidRequest("redirect_uri does no correspond") + } + return authReq, client, nil +} + +//AuthorizeCodeClient checks the authorization of the client and that the used method was the one previously registered. +//It than returns the auth request corresponding to the auth code +func AuthorizeCodeClient(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exchanger Exchanger) (request AuthRequest, client Client, err error) { + if tokenReq.ClientAssertionType == oidc.ClientAssertionTypeJWTAssertion { + jwtExchanger, ok := exchanger.(JWTAuthorizationGrantExchanger) + if !ok || !exchanger.AuthMethodPrivateKeyJWTSupported() { + return nil, nil, errors.New("auth_method private_key_jwt not supported") + } + client, err = AuthorizePrivateJWTKey(ctx, tokenReq.ClientAssertion, jwtExchanger) + if err != nil { + return nil, nil, err + } + request, err = AuthRequestByCode(ctx, exchanger.Storage(), tokenReq.Code) + return request, client, err + } + client, err = exchanger.Storage().GetClientByClientID(ctx, tokenReq.ClientID) + if err != nil { + return nil, nil, err + } + if client.AuthMethod() == oidc.AuthMethodPrivateKeyJWT { + return nil, nil, errors.New("invalid_grant") + } + if client.AuthMethod() == oidc.AuthMethodNone { + request, err = AuthRequestByCode(ctx, exchanger.Storage(), tokenReq.Code) + if err != nil { + return nil, nil, err + } + err = AuthorizeCodeChallenge(tokenReq, request.GetCodeChallenge()) + return request, client, err + } + if client.AuthMethod() == oidc.AuthMethodPost && !exchanger.AuthMethodPostSupported() { + return nil, nil, errors.New("auth_method post not supported") + } + err = AuthorizeClientIDSecret(ctx, tokenReq.ClientID, tokenReq.ClientSecret, exchanger.Storage()) + request, err = AuthRequestByCode(ctx, exchanger.Storage(), tokenReq.Code) + return request, client, err +} + +//AuthRequestByCode returns the AuthRequest previously created from Storage corresponding to the auth code or an error +func AuthRequestByCode(ctx context.Context, storage Storage, code string) (AuthRequest, error) { + authReq, err := storage.AuthRequestByCode(ctx, code) + if err != nil { + return nil, ErrInvalidRequest("invalid code") + } + return authReq, nil +} diff --git a/pkg/op/token_exchange.go b/pkg/op/token_exchange.go new file mode 100644 index 0000000..8d93e0c --- /dev/null +++ b/pkg/op/token_exchange.go @@ -0,0 +1,30 @@ +package op + +import ( + "errors" + "net/http" + + "github.com/caos/oidc/pkg/oidc" +) + +//TokenExchange will handle the OAuth 2.0 token exchange grant ("urn:ietf:params:oauth:grant-type:token-exchange") +func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + tokenRequest, err := ParseTokenExchangeRequest(w, r) + if err != nil { + RequestError(w, r, err) + return + } + err = ValidateTokenExchangeRequest(tokenRequest, exchanger.Storage()) + if err != nil { + RequestError(w, r, err) + return + } +} + +func ParseTokenExchangeRequest(w http.ResponseWriter, r *http.Request) (oidc.TokenRequest, error) { + return nil, errors.New("Unimplemented") //TODO: impl +} + +func ValidateTokenExchangeRequest(tokenReq oidc.TokenRequest, storage Storage) error { + return errors.New("Unimplemented") //TODO: impl +} diff --git a/pkg/op/token_jwt_profile.go b/pkg/op/token_jwt_profile.go new file mode 100644 index 0000000..c96d4b2 --- /dev/null +++ b/pkg/op/token_jwt_profile.go @@ -0,0 +1,79 @@ +package op + +import ( + "context" + "net/http" + "time" + + "github.com/caos/oidc/pkg/oidc" + "github.com/caos/oidc/pkg/utils" +) + +type JWTAuthorizationGrantExchanger interface { + Exchanger + JWTProfileVerifier() JWTProfileVerifier +} + +//JWTProfile handles the OAuth 2.0 JWT Profile Authorization Grant https://tools.ietf.org/html/rfc7523#section-2.1 +func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger JWTAuthorizationGrantExchanger) { + profileRequest, err := ParseJWTProfileGrantRequest(r, exchanger.Decoder()) + if err != nil { + RequestError(w, r, err) + } + + tokenRequest, err := VerifyJWTAssertion(r.Context(), profileRequest.Assertion, exchanger.JWTProfileVerifier()) + if err != nil { + RequestError(w, r, err) + return + } + + tokenRequest.Scopes, err = exchanger.Storage().ValidateJWTProfileScopes(r.Context(), tokenRequest.Issuer, profileRequest.Scope) + if err != nil { + RequestError(w, r, err) + return + } + resp, err := CreateJWTTokenResponse(r.Context(), tokenRequest, exchanger) + if err != nil { + RequestError(w, r, err) + return + } + utils.MarshalJSON(w, resp) +} + +func ParseJWTProfileGrantRequest(r *http.Request, decoder utils.Decoder) (*oidc.JWTProfileGrantRequest, error) { + err := r.ParseForm() + if err != nil { + return nil, ErrInvalidRequest("error parsing form") + } + tokenReq := new(oidc.JWTProfileGrantRequest) + err = decoder.Decode(tokenReq, r.Form) + if err != nil { + return nil, ErrInvalidRequest("error decoding form") + } + return tokenReq, nil +} + +//CreateJWTTokenResponse creates +func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator) (*oidc.AccessTokenResponse, error) { + id, exp, err := creator.Storage().CreateToken(ctx, tokenRequest) + if err != nil { + return nil, err + } + accessToken, err := CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto()) + if err != nil { + return nil, err + } + + return &oidc.AccessTokenResponse{ + AccessToken: accessToken, + TokenType: oidc.BearerToken, + ExpiresIn: uint64(exp.Sub(time.Now().UTC()).Seconds()), + }, nil +} + +//ParseJWTProfileRequest has been renamed to ParseJWTProfileGrantRequest +// +//deprecated: use ParseJWTProfileGrantRequest +func ParseJWTProfileRequest(r *http.Request, decoder utils.Decoder) (*oidc.JWTProfileGrantRequest, error) { + return ParseJWTProfileGrantRequest(r, decoder) +} diff --git a/pkg/op/token_refresh.go b/pkg/op/token_refresh.go new file mode 100644 index 0000000..5f3c3a5 --- /dev/null +++ b/pkg/op/token_refresh.go @@ -0,0 +1,113 @@ +package op + +import ( + "context" + "errors" + "net/http" + "time" + + "github.com/caos/oidc/pkg/oidc" + "github.com/caos/oidc/pkg/utils" +) + +type RefreshTokenRequest interface { + //GetID() string + //GetACR() string + //GetAMR() []string + GetAudience() []string + GetAuthTime() time.Time + GetClientID() string + GetScopes() []string + GetSubject() string + //GetRefreshToken() string +} + +//RefreshTokenExchange handles the OAuth 2.0 refresh_token grant, including +//parsing, validating, authorizing the client and finally exchanging the refresh_token for new tokens +func RefreshTokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + tokenReq, err := ParseRefreshTokenRequest(r, exchanger.Decoder()) + if err != nil { + RequestError(w, r, err) + } + authReq, client, err := ValidateRefreshTokenRequest(r.Context(), tokenReq, exchanger) + if err != nil { + RequestError(w, r, err) + return + } + resp, err := CreateTokenResponse(r.Context(), authReq, client, exchanger, true, "") + if err != nil { + RequestError(w, r, err) + return + } + utils.MarshalJSON(w, resp) +} + +//ParseRefreshTokenRequest parsed the http request into a oidc.RefreshTokenRequest +func ParseRefreshTokenRequest(r *http.Request, decoder utils.Decoder) (*oidc.RefreshTokenRequest, error) { + request := new(oidc.RefreshTokenRequest) + err := ParseAuthenticatedTokenRequest(r, decoder, request) + if err != nil { + return nil, err + } + return request, nil +} + +//ValidateRefreshTokenRequest validates the refresh_token request parameters including authorization check of the client +//and returns the data representing the original auth request corresponding to the refresh_token +func ValidateRefreshTokenRequest(ctx context.Context, tokenReq *oidc.RefreshTokenRequest, exchanger Exchanger) (RefreshTokenRequest, Client, error) { + if tokenReq.RefreshToken == "" { + return nil, nil, ErrInvalidRequest("code missing") + } + authReq, client, err := AuthorizeRefreshClient(ctx, tokenReq, exchanger) + if err != nil { + return nil, nil, err + } + if client.GetID() != authReq.GetClientID() { + return nil, nil, ErrInvalidRequest("invalid auth code") + } + return authReq, client, nil +} + +//AuthorizeCodeClient checks the authorization of the client and that the used method was the one previously registered. +//It than returns the data representing the original auth request corresponding to the refresh_token +func AuthorizeRefreshClient(ctx context.Context, tokenReq *oidc.RefreshTokenRequest, exchanger Exchanger) (request RefreshTokenRequest, client Client, err error) { + if tokenReq.ClientAssertionType == oidc.ClientAssertionTypeJWTAssertion { + jwtExchanger, ok := exchanger.(JWTAuthorizationGrantExchanger) + if !ok || !exchanger.AuthMethodPrivateKeyJWTSupported() { + return nil, nil, errors.New("auth_method private_key_jwt not supported") + } + client, err = AuthorizePrivateJWTKey(ctx, tokenReq.ClientAssertion, jwtExchanger) + if err != nil { + return nil, nil, err + } + request, err = RefreshTokenRequestByRefreshToken(ctx, exchanger.Storage(), tokenReq.RefreshToken) + return request, client, err + } + client, err = exchanger.Storage().GetClientByClientID(ctx, tokenReq.ClientID) + if err != nil { + return nil, nil, err + } + if client.AuthMethod() == oidc.AuthMethodPrivateKeyJWT { + return nil, nil, errors.New("invalid_grant") + } + if client.AuthMethod() == oidc.AuthMethodNone { + request, err = RefreshTokenRequestByRefreshToken(ctx, exchanger.Storage(), tokenReq.RefreshToken) + return request, client, err + } + if client.AuthMethod() == oidc.AuthMethodPost && !exchanger.AuthMethodPostSupported() { + return nil, nil, errors.New("auth_method post not supported") + } + err = AuthorizeClientIDSecret(ctx, tokenReq.ClientID, tokenReq.ClientSecret, exchanger.Storage()) + request, err = RefreshTokenRequestByRefreshToken(ctx, exchanger.Storage(), tokenReq.RefreshToken) + return request, client, err +} + +//RefreshTokenRequestByRefreshToken returns the RefreshTokenRequest (data representing the original auth request) +//corresponding to the refresh_token from Storage or an error +func RefreshTokenRequestByRefreshToken(ctx context.Context, storage Storage, refreshToken string) (RefreshTokenRequest, error) { + authReq, err := storage.RefreshTokenRequestByRefreshToken(ctx, refreshToken) + if err != nil { + return nil, ErrInvalidRequest("invalid refreshToken") + } + return authReq, nil +} diff --git a/pkg/op/token_request.go b/pkg/op/token_request.go new file mode 100644 index 0000000..f8148d7 --- /dev/null +++ b/pkg/op/token_request.go @@ -0,0 +1,121 @@ +package op + +import ( + "context" + "net/http" + "net/url" + + "github.com/caos/oidc/pkg/oidc" + "github.com/caos/oidc/pkg/utils" +) + +type Exchanger interface { + Issuer() string + Storage() Storage + Decoder() utils.Decoder + Signer() Signer + Crypto() Crypto + AuthMethodPostSupported() bool + AuthMethodPrivateKeyJWTSupported() bool + GrantTypeTokenExchangeSupported() bool + GrantTypeJWTAuthorizationSupported() bool +} + +func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + switch r.FormValue("grant_type") { + case string(oidc.GrantTypeCode): + CodeExchange(w, r, exchanger) + return + case string(oidc.GrantTypeRefreshToken): + RefreshTokenExchange(w, r, exchanger) + return + case string(oidc.GrantTypeBearer): + if ex, ok := exchanger.(JWTAuthorizationGrantExchanger); ok && exchanger.GrantTypeJWTAuthorizationSupported() { + JWTProfile(w, r, ex) + return + } + case string(oidc.GrantTypeTokenExchange): + if exchanger.GrantTypeTokenExchangeSupported() { + TokenExchange(w, r, exchanger) + return + } + case "": + RequestError(w, r, ErrInvalidRequest("grant_type missing")) + return + } + RequestError(w, r, ErrInvalidRequest("grant_type not supported")) + } +} + +//authenticatedTokenRequest is a helper interface for ParseAuthenticatedTokenRequest +//it is implemented by oidc.AuthRequest and oidc.RefreshTokenRequest +type AuthenticatedTokenRequest interface { + SetClientID(string) + SetClientSecret(string) +} + +//ParseAuthenticatedTokenRequest parses the client_id and client_secret from the HTTP request from either +//HTTP Basic Auth header or form body and sets them into the provided authenticatedTokenRequest interface +func ParseAuthenticatedTokenRequest(r *http.Request, decoder utils.Decoder, request AuthenticatedTokenRequest) error { + err := r.ParseForm() + if err != nil { + return ErrInvalidRequest("error parsing form") + } + err = decoder.Decode(request, r.Form) + if err != nil { + return ErrInvalidRequest("error decoding form") + } + clientID, clientSecret, ok := r.BasicAuth() + if ok { + clientID, err = url.QueryUnescape(clientID) + if err != nil { + return ErrInvalidRequest("invalid basic auth header") + } + clientSecret, err = url.QueryUnescape(clientSecret) + if err != nil { + return ErrInvalidRequest("invalid basic auth header") + } + request.SetClientID(clientID) + request.SetClientSecret(clientSecret) + } + return nil +} + +//AuthorizeRefreshClientByClientIDSecret authorizes a client by validating the client_id and client_secret (Basic Auth and POST) +func AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string, storage Storage) error { + err := storage.AuthorizeClientIDSecret(ctx, clientID, clientSecret) + if err != nil { + return err //TODO: wrap? + } + return nil +} + +//AuthorizeCodeClientByCodeChallenge authorizes a client by validating the code_verifier against the previously sent +//code_challenge of the auth request (PKCE) +func AuthorizeCodeChallenge(tokenReq *oidc.AccessTokenRequest, challenge *oidc.CodeChallenge) error { + if tokenReq.CodeVerifier == "" { + return ErrInvalidRequest("code_challenge required") + } + if !oidc.VerifyCodeChallenge(challenge, tokenReq.CodeVerifier) { + return ErrInvalidRequest("code_challenge invalid") + } + return nil +} + +//AuthorizePrivateJWTKey authorizes a client by validating the client_assertion's signature with a previously +//registered public key (JWT Profile) +func AuthorizePrivateJWTKey(ctx context.Context, clientAssertion string, exchanger JWTAuthorizationGrantExchanger) (Client, error) { + jwtReq, err := VerifyJWTAssertion(ctx, clientAssertion, exchanger.JWTProfileVerifier()) + if err != nil { + return nil, err + } + client, err := exchanger.Storage().GetClientByClientID(ctx, jwtReq.Issuer) + if err != nil { + return nil, err + } + if client.AuthMethod() != oidc.AuthMethodPrivateKeyJWT { + return nil, ErrInvalidRequest("invalid_client") + } + return client, nil +} diff --git a/pkg/op/tokenrequest.go b/pkg/op/tokenrequest.go deleted file mode 100644 index b51d2c8..0000000 --- a/pkg/op/tokenrequest.go +++ /dev/null @@ -1,242 +0,0 @@ -package op - -import ( - "context" - "errors" - "net/http" - "net/url" - - "github.com/caos/oidc/pkg/oidc" - "github.com/caos/oidc/pkg/utils" -) - -type Exchanger interface { - Issuer() string - Storage() Storage - Decoder() utils.Decoder - Signer() Signer - Crypto() Crypto - AuthMethodPostSupported() bool - AuthMethodPrivateKeyJWTSupported() bool - GrantTypeTokenExchangeSupported() bool - GrantTypeJWTAuthorizationSupported() bool -} - -type JWTAuthorizationGrantExchanger interface { - Exchanger - JWTProfileVerifier() JWTProfileVerifier -} - -func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - switch r.FormValue("grant_type") { - case string(oidc.GrantTypeCode): - CodeExchange(w, r, exchanger) - return - case string(oidc.GrantTypeBearer): - if ex, ok := exchanger.(JWTAuthorizationGrantExchanger); ok && exchanger.GrantTypeJWTAuthorizationSupported() { - JWTProfile(w, r, ex) - return - } - case string(oidc.GrantTypeTokenExchange): - if exchanger.GrantTypeTokenExchangeSupported() { - TokenExchange(w, r, exchanger) - return - } - case "": - RequestError(w, r, ErrInvalidRequest("grant_type missing")) - return - } - RequestError(w, r, ErrInvalidRequest("grant_type not supported")) - } -} - -func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { - tokenReq, err := ParseAccessTokenRequest(r, exchanger.Decoder()) - if err != nil { - RequestError(w, r, err) - } - if tokenReq.Code == "" { - RequestError(w, r, ErrInvalidRequest("code missing")) - return - } - authReq, client, err := ValidateAccessTokenRequest(r.Context(), tokenReq, exchanger) - if err != nil { - RequestError(w, r, err) - return - } - resp, err := CreateTokenResponse(r.Context(), authReq, client, exchanger, true, tokenReq.Code) - if err != nil { - RequestError(w, r, err) - return - } - utils.MarshalJSON(w, resp) -} - -func ParseAccessTokenRequest(r *http.Request, decoder utils.Decoder) (*oidc.AccessTokenRequest, error) { - err := r.ParseForm() - if err != nil { - return nil, ErrInvalidRequest("error parsing form") - } - tokenReq := new(oidc.AccessTokenRequest) - err = decoder.Decode(tokenReq, r.Form) - if err != nil { - return nil, ErrInvalidRequest("error decoding form") - } - clientID, clientSecret, ok := r.BasicAuth() - if ok { - tokenReq.ClientID, err = url.QueryUnescape(clientID) - if err != nil { - return nil, ErrInvalidRequest("invalid basic auth header") - } - tokenReq.ClientSecret, err = url.QueryUnescape(clientSecret) - if err != nil { - return nil, ErrInvalidRequest("invalid basic auth header") - } - } - return tokenReq, nil -} - -func ValidateAccessTokenRequest(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exchanger Exchanger) (AuthRequest, Client, error) { - authReq, client, err := AuthorizeClient(ctx, tokenReq, exchanger) - if err != nil { - return nil, nil, err - } - if client.GetID() != authReq.GetClientID() { - return nil, nil, ErrInvalidRequest("invalid auth code") - } - if tokenReq.RedirectURI != authReq.GetRedirectURI() { - return nil, nil, ErrInvalidRequest("redirect_uri does no correspond") - } - return authReq, client, nil -} - -func AuthorizeClient(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exchanger Exchanger) (AuthRequest, Client, error) { - if tokenReq.ClientAssertionType == oidc.ClientAssertionTypeJWTAssertion { - jwtExchanger, ok := exchanger.(JWTAuthorizationGrantExchanger) - if !ok || !exchanger.AuthMethodPrivateKeyJWTSupported() { - return nil, nil, errors.New("auth_method private_key_jwt not supported") - } - return AuthorizePrivateJWTKey(ctx, tokenReq, jwtExchanger) - } - client, err := exchanger.Storage().GetClientByClientID(ctx, tokenReq.ClientID) - if err != nil { - return nil, nil, err - } - if client.AuthMethod() == oidc.AuthMethodPrivateKeyJWT { - return nil, nil, errors.New("invalid_grant") - } - if client.AuthMethod() == oidc.AuthMethodNone { - authReq, err := AuthorizeCodeChallenge(ctx, tokenReq, exchanger) - return authReq, client, err - } - if client.AuthMethod() == oidc.AuthMethodPost && !exchanger.AuthMethodPostSupported() { - return nil, nil, errors.New("auth_method post not supported") - } - authReq, err := AuthorizeClientIDSecret(ctx, tokenReq.ClientID, tokenReq.ClientSecret, tokenReq.Code, exchanger.Storage()) - return authReq, client, err -} - -func AuthorizePrivateJWTKey(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exchanger JWTAuthorizationGrantExchanger) (AuthRequest, Client, error) { - jwtReq, err := VerifyJWTAssertion(ctx, tokenReq.ClientAssertion, exchanger.JWTProfileVerifier()) - if err != nil { - return nil, nil, err - } - authReq, err := exchanger.Storage().AuthRequestByCode(ctx, tokenReq.Code) - if err != nil { - return nil, nil, ErrInvalidRequest("invalid code") - } - client, err := exchanger.Storage().GetClientByClientID(ctx, jwtReq.Issuer) - if err != nil { - return nil, nil, err - } - if client.AuthMethod() != oidc.AuthMethodPrivateKeyJWT { - return nil, nil, ErrInvalidRequest("invalid_client") - } - return authReq, client, nil -} - -func AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret, code string, storage Storage) (AuthRequest, error) { - err := storage.AuthorizeClientIDSecret(ctx, clientID, clientSecret) - if err != nil { - return nil, err - } - authReq, err := storage.AuthRequestByCode(ctx, code) - if err != nil { - return nil, ErrInvalidRequest("invalid code") - } - return authReq, nil -} - -func AuthorizeCodeChallenge(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exchanger Exchanger) (AuthRequest, error) { - if tokenReq.CodeVerifier == "" { - return nil, ErrInvalidRequest("code_challenge required") - } - authReq, err := exchanger.Storage().AuthRequestByCode(ctx, tokenReq.Code) - if err != nil { - return nil, ErrInvalidRequest("invalid code") - } - if !oidc.VerifyCodeChallenge(authReq.GetCodeChallenge(), tokenReq.CodeVerifier) { - return nil, ErrInvalidRequest("code_challenge invalid") - } - return authReq, nil -} - -func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger JWTAuthorizationGrantExchanger) { - profileRequest, err := ParseJWTProfileRequest(r, exchanger.Decoder()) - if err != nil { - RequestError(w, r, err) - } - - tokenRequest, err := VerifyJWTAssertion(r.Context(), profileRequest.Assertion, exchanger.JWTProfileVerifier()) - if err != nil { - RequestError(w, r, err) - return - } - - tokenRequest.Scopes, err = exchanger.Storage().ValidateJWTProfileScopes(r.Context(), tokenRequest.Issuer, profileRequest.Scope) - if err != nil { - RequestError(w, r, err) - return - } - resp, err := CreateJWTTokenResponse(r.Context(), tokenRequest, exchanger) - if err != nil { - RequestError(w, r, err) - return - } - utils.MarshalJSON(w, resp) -} - -func ParseJWTProfileRequest(r *http.Request, decoder utils.Decoder) (*oidc.JWTProfileGrantRequest, error) { - err := r.ParseForm() - if err != nil { - return nil, ErrInvalidRequest("error parsing form") - } - tokenReq := new(oidc.JWTProfileGrantRequest) - err = decoder.Decode(tokenReq, r.Form) - if err != nil { - return nil, ErrInvalidRequest("error decoding form") - } - return tokenReq, nil -} - -func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { - tokenRequest, err := ParseTokenExchangeRequest(w, r) - if err != nil { - RequestError(w, r, err) - return - } - err = ValidateTokenExchangeRequest(tokenRequest, exchanger.Storage()) - if err != nil { - RequestError(w, r, err) - return - } -} - -func ParseTokenExchangeRequest(w http.ResponseWriter, r *http.Request) (oidc.TokenRequest, error) { - return nil, errors.New("Unimplemented") //TODO: impl -} - -func ValidateTokenExchangeRequest(tokenReq oidc.TokenRequest, storage Storage) error { - return errors.New("Unimplemented") //TODO: impl -} diff --git a/pkg/op/verifier_jwt_profile.go b/pkg/op/verifier_jwt_profile.go index 338e39a..f7939b5 100644 --- a/pkg/op/verifier_jwt_profile.go +++ b/pkg/op/verifier_jwt_profile.go @@ -23,6 +23,7 @@ type jwtProfileVerifier struct { offset time.Duration } +//NewJWTProfileVerifier creates a oidc.Verifier for JWT Profile assertions (authorization grant and client authentication) func NewJWTProfileVerifier(storage Storage, issuer string, maxAgeIAT, offset time.Duration) JWTProfileVerifier { return &jwtProfileVerifier{ storage: storage, @@ -48,6 +49,9 @@ func (v *jwtProfileVerifier) Offset() time.Duration { return v.offset } +//VerifyJWTAssertion verifies the assertion string from JWT Profile (authorization grant and client authentication) +// +//checks audience, exp, iat, signature and that issuer and sub are the same func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerifier) (*oidc.JWTTokenRequest, error) { request := new(oidc.JWTTokenRequest) payload, err := oidc.ParseToken(assertion, request) @@ -85,6 +89,7 @@ type jwtProfileKeySet struct { userID string } +//VerifySignature implements oidc.KeySet by getting the public key from Storage implementation func (k *jwtProfileKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (payload []byte, err error) { keyID := "" for _, sig := range jws.Signatures {