2323#
2424# You should have received a copy of the GNU General Public License
2525# along with this program; if not, write to the Free Software
26- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2727#
2828# See COPYRIGHT and LICENSE files for more details.
2929#++
6464
6565 context "with an invalid access token" do
6666 let ( :oauth_access_token ) { "1337" }
67+ let ( :expected_www_auth_header ) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
6768
6869 it "returns unauthorized" do
6970 expect ( last_response ) . to have_http_status :unauthorized
70- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="invalid_token"' )
71+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
7172 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
7273 end
7374 end
7475
7576 context "with a revoked access token" do
7677 let ( :token ) { create ( :oauth_access_token , resource_owner : user , revoked_at : DateTime . now ) }
7778 let ( :oauth_access_token ) { token . plaintext_token }
79+ let ( :expected_www_auth_header ) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
7880
7981 it "returns unauthorized" do
8082 expect ( last_response ) . to have_http_status :unauthorized
81- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="invalid_token"' )
83+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
8284 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
8385 end
8486 end
8587
8688 context "when the token's application is disabled" do
8789 let ( :token ) { create ( :oauth_access_token , resource_owner : user , application : create ( :oauth_application , enabled : false ) ) }
8890 let ( :oauth_access_token ) { token . plaintext_token }
91+ let ( :expected_www_auth_header ) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
8992
9093 it "returns unauthorized" do
9194 expect ( last_response ) . to have_http_status :unauthorized
92- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="invalid_token"' )
95+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
9396 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
9497 end
9598 end
9699
97100 context "with an expired access token" do
98101 let ( :token ) { create ( :oauth_access_token , resource_owner : user ) }
99102 let ( :oauth_access_token ) { token . plaintext_token }
103+ let ( :expected_www_auth_header ) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
100104
101105 around do |ex |
102106 Timecop . freeze ( Time . current + ( token . expires_in + 5 ) . seconds ) do
106110
107111 it "returns unauthorized" do
108112 expect ( last_response ) . to have_http_status :unauthorized
109- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="invalid_token"' )
113+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
110114 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
111115 end
112116 end
113117
114118 context "with wrong scope" do
115119 let ( :token ) { create ( :oauth_access_token , resource_owner : user , scopes : "unknown_scope" ) }
116120 let ( :oauth_access_token ) { token . plaintext_token }
121+ let ( :expected_www_auth_header ) { 'Bearer realm="OpenProject API", scope="api_v3", error="insufficient_scope"' }
117122
118123 it "returns forbidden" do
119124 expect ( last_response ) . to have_http_status :forbidden
120- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="insufficient_scope"' )
125+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
121126 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
122127 end
123128 end
126131 let ( :token ) { create ( :oauth_access_token , resource_owner : user , application :) }
127132 let ( :application ) { create ( :oauth_application ) }
128133 let ( :oauth_access_token ) { token . plaintext_token }
134+ let ( :expected_www_auth_header ) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
129135
130136 around do |ex |
131137 user . destroy
134140
135141 it "returns unauthorized" do
136142 expect ( last_response ) . to have_http_status :unauthorized
137- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="invalid_token"' )
143+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
138144 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
139145 end
140146
143149
144150 it "returns unauthorized" do
145151 expect ( last_response ) . to have_http_status :unauthorized
146- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="invalid_token"' )
152+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
147153 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
148154 end
149155 end
153159 let ( :token ) { create ( :oauth_access_token , resource_owner : user ) }
154160 let ( :oauth_access_token ) { token . plaintext_token }
155161 let ( :user ) { create ( :user , :locked ) }
162+ let ( :expected_www_auth_header ) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
156163
157164 it "returns unauthorized" do
158165 expect ( last_response ) . to have_http_status :unauthorized
159- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="invalid_token"' )
166+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
160167 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
161168 end
162169 end
189196 context "and the client credentials user is locked" do
190197 let ( :user ) { create ( :user , :locked ) }
191198 let ( :expected_message ) { "You did not provide the correct credentials." }
199+ let ( :expected_www_auth_header ) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
192200
193201 it "returns unauthorized" do
194202 expect ( last_response ) . to have_http_status :unauthorized
195- expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( 'Bearer realm="OpenProject API", error="invalid_token"' )
203+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
196204 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
197205 end
198206 end
@@ -491,6 +499,11 @@ def set_basic_auth_header(user, password)
491499 let ( :token_issuer ) { "https://keycloak.local/realms/master" }
492500 let ( :token_scope ) { "email profile api_v3" }
493501 let ( :expected_message ) { "You did not provide the correct credentials." }
502+ let ( :expected_www_auth_header ) do
503+ "Bearer realm=\" OpenProject API\" , scope=\" api_v3\" , error=\" #{ expected_error } \" , " \
504+ "error_description=\" #{ expected_error_description } \" "
505+ end
506+ let ( :expected_error ) { "invalid_token" }
494507 let ( :keys_request_stub ) do
495508 stub_request ( :get , "https://keycloak.local/realms/master/protocol/openid-connect/certs" )
496509 . to_return ( status : 200 , body : JWT ::JWK ::Set . new ( jwk_response ) . export . to_json , headers : { } )
@@ -514,66 +527,64 @@ def set_basic_auth_header(user, password)
514527
515528 context "when token is issued by provider not configured in OP" do
516529 let ( :token_issuer ) { "https://eve.example.com" }
530+ let ( :expected_error_description ) { "The access token issuer is unknown" }
517531
518532 it "fails with HTTP 401 Unauthorized" do
519533 get resource
520534 expect ( last_response ) . to have_http_status :unauthorized
521- expect ( last_response . header [ "WWW-Authenticate" ] )
522- . to eq ( %{Bearer realm="OpenProject API", error="invalid_token", error_description="The access token issuer is unknown"} )
535+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
523536 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
524537 end
525538 end
526539
527540 context "when token signature algorithm is not supported" do
528541 let ( :token ) { JWT . encode ( payload , "secret" , "HS256" , { kid : "97AmyvoS8BFFRfm585GPgA16G1H2V22EdxxuAYUuoKk" } ) }
542+ let ( :expected_error_description ) { "Token signature algorithm HS256 is not supported" }
529543
530544 it "fails with HTTP 401 Unauthorized" do
531545 get resource
532546 expect ( last_response ) . to have_http_status :unauthorized
533- error = "Token signature algorithm HS256 is not supported"
534- expect ( last_response . header [ "WWW-Authenticate" ] )
535- . to eq ( %{Bearer realm="OpenProject API", error="invalid_token", error_description="#{ error } "} )
547+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
536548 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
537549 end
538550 end
539551
540552 context "when aud does not contain client_id" do
541553 let ( :token_aud ) { [ "Lisa" , "Bart" ] }
554+ let ( :expected_error_description ) { 'Invalid audience. Expected https://openproject.local, received [\"Lisa\", \"Bart\"]' }
542555
543556 it "fails with HTTP 401 Unauthorized" do
544557 get resource
545558
546559 expect ( last_response ) . to have_http_status :unauthorized
547- error = 'Invalid audience. Expected https://openproject.local, received ["Lisa", "Bart"]'
548- expect ( last_response . header [ "WWW-Authenticate" ] )
549- . to eq ( %{Bearer realm="OpenProject API", error="invalid_token", error_description="#{ error } "} )
560+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
550561 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
551562 end
552563 end
553564
554565 context "when the scope does not permit access to APIv3" do
555566 let ( :token_scope ) { "profile email" }
567+ let ( :expected_error ) { "insufficient_scope" }
568+ let ( :expected_error_description ) { "Requires scope api_v3 to access this resource." }
556569
557570 it "fails with HTTP 403 Forbidden" do
558571 get resource
559572
560573 expect ( last_response ) . to have_http_status :forbidden
561- error = "Requires scope api_v3 to access this resource."
562- expect ( last_response . header [ "WWW-Authenticate" ] )
563- . to eq ( %{Bearer realm="OpenProject API", error="insufficient_scope", error_description="#{ error } "} )
574+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
564575 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
565576 end
566577 end
567578
568579 context "when access token has expired already" do
569580 let ( :token_exp ) { 5 . minutes . ago }
581+ let ( :expected_error_description ) { "Signature has expired" }
570582
571583 it "fails with HTTP 401 Unauthorized" do
572584 get resource
573585
574586 expect ( last_response ) . to have_http_status :unauthorized
575- expect ( last_response . header [ "WWW-Authenticate" ] )
576- . to eq ( %{Bearer realm="OpenProject API", error="invalid_token", error_description="Signature has expired"} )
587+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
577588 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
578589 end
579590
@@ -590,40 +601,37 @@ def set_basic_auth_header(user, password)
590601
591602 context "when kid is absent in keycloak keys response" do
592603 let ( :jwk_response ) { JWT ::JWK . new ( OpenSSL ::PKey ::RSA . new ( 2048 ) , kid : "your-kid" , use : "sig" , alg : "RS256" ) }
604+ let ( :expected_error_description ) { "The signature key ID is unknown" }
593605
594606 it "fails with HTTP 401 Unauthorized" do
595607 get resource
596608 expect ( last_response ) . to have_http_status :unauthorized
609+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
597610 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
598- error = "The signature key ID is unknown"
599- expect ( last_response . header [ "WWW-Authenticate" ] )
600- . to eq ( %{Bearer realm="OpenProject API", error="invalid_token", error_description="#{ error } "} )
601611 end
602612 end
603613
604614 context "when user identified by token is not known" do
605615 let ( :user ) { create ( :user , authentication_provider : create ( :oidc_provider ) ) }
616+ let ( :expected_error_description ) { "The user identified by the token is not known" }
606617
607618 it "fails with HTTP 401 Unauthorized" do
608619 get resource
609620 expect ( last_response ) . to have_http_status :unauthorized
621+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
610622 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
611- error = "The user identified by the token is not known"
612- expect ( last_response . header [ "WWW-Authenticate" ] )
613- . to eq ( %{Bearer realm="OpenProject API", error="invalid_token", error_description="#{ error } "} )
614623 end
615624 end
616625
617626 context "when user identified by token is locked" do
618627 let ( :user ) { create ( :user , :locked , authentication_provider : create ( :oidc_provider ) , external_id : token_sub ) }
628+ let ( :expected_error_description ) { "The user account is locked" }
619629
620630 it "fails with HTTP 401 Unauthorized" do
621631 get resource
622632 expect ( last_response ) . to have_http_status :unauthorized
633+ expect ( last_response . header [ "WWW-Authenticate" ] ) . to eq ( expected_www_auth_header )
623634 expect ( JSON . parse ( last_response . body ) ) . to eq ( error_response_body )
624- error = "The user account is locked"
625- expect ( last_response . header [ "WWW-Authenticate" ] )
626- . to eq ( %{Bearer realm="OpenProject API", error="invalid_token", error_description="#{ error } "} )
627635 end
628636 end
629637 end
0 commit comments