mirror of https://github.com/grafana/grafana.git
				
				
				
			ext_jwt: switch to new authlib (#87157)
This commit is contained in:
		
							parent
							
								
									babfa2beac
								
							
						
					
					
						commit
						0c59baf62d
					
				
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							| 
						 | 
					@ -87,7 +87,7 @@ require (
 | 
				
			||||||
	github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
 | 
						github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
 | 
				
			||||||
	github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad
 | 
						github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad
 | 
				
			||||||
	github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36 // @grafana/alerting-squad-backend
 | 
						github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36 // @grafana/alerting-squad-backend
 | 
				
			||||||
	github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 // @grafana/identity-access-team
 | 
						github.com/grafana/authlib v0.0.0-20240503035720-d1f918d6254a // @grafana/identity-access-team
 | 
				
			||||||
	github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad
 | 
						github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad
 | 
				
			||||||
	github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
 | 
						github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
 | 
				
			||||||
	github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
 | 
						github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										4
									
								
								go.sum
								
								
								
								
							| 
						 | 
					@ -2150,8 +2150,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
 | 
				
			||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
					github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
				
			||||||
github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36 h1:v4aQ0cde8SCzNRrD2RczzmFolEkXWriSY9tKakAD0ng=
 | 
					github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36 h1:v4aQ0cde8SCzNRrD2RczzmFolEkXWriSY9tKakAD0ng=
 | 
				
			||||||
github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36/go.mod h1:8nOsn7PWmttOmWiR7bvYIl3VLl+tIq72ZF+1y54w36M=
 | 
					github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36/go.mod h1:8nOsn7PWmttOmWiR7bvYIl3VLl+tIq72ZF+1y54w36M=
 | 
				
			||||||
github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 h1:lGEuhD/KhhN1OiPrvwQejl9Lg8MvaHdj3lHZNref4is=
 | 
					github.com/grafana/authlib v0.0.0-20240503035720-d1f918d6254a h1:9I4HHhjS634CWcsCS82wGvpkveypCzMe+2DMuzKoW5A=
 | 
				
			||||||
github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4=
 | 
					github.com/grafana/authlib v0.0.0-20240503035720-d1f918d6254a/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4=
 | 
				
			||||||
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
 | 
					github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
 | 
				
			||||||
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
 | 
					github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
 | 
				
			||||||
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=
 | 
					github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										41
									
								
								go.work.sum
								
								
								
								
							
							
						
						
									
										41
									
								
								go.work.sum
								
								
								
								
							| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1 h1:tdpHgTbmbvEIARu+bixzmleMi14+3imnpoFXz+Qzjp4=
 | 
					buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230721003620-2341cbb21958.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew=
 | 
				
			||||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew=
 | 
					buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew=
 | 
				
			||||||
buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1 h1:vp9EaPFSb75qe/793x58yE5fY1IJ/gdxb/kcDUzavtI=
 | 
					buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1 h1:vp9EaPFSb75qe/793x58yE5fY1IJ/gdxb/kcDUzavtI=
 | 
				
			||||||
buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1/go.mod h1:YDq2B5X5BChU0lxAG5MxHpDb8mx1fv9OGtF2mwOe7hY=
 | 
					buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1/go.mod h1:YDq2B5X5BChU0lxAG5MxHpDb8mx1fv9OGtF2mwOe7hY=
 | 
				
			||||||
| 
						 | 
					@ -512,19 +512,15 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ
 | 
				
			||||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
 | 
					github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
 | 
				
			||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
 | 
					github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
 | 
				
			||||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
 | 
					github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
 | 
				
			||||||
github.com/bufbuild/protovalidate-go v0.2.1 h1:pJr07sYhliyfj/STAM7hU4J3FKpVeLVKvOBmOTN8j+s=
 | 
					 | 
				
			||||||
github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4cEMKCSSb8ZCkin+MVA=
 | 
					github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4cEMKCSSb8ZCkin+MVA=
 | 
				
			||||||
github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw=
 | 
					github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw=
 | 
				
			||||||
github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0=
 | 
					 | 
				
			||||||
github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
 | 
					github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
 | 
				
			||||||
github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw=
 | 
					github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw=
 | 
				
			||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 | 
					github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 | 
				
			||||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
 | 
					github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
 | 
				
			||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 | 
					github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 | 
				
			||||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
 | 
					github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
 | 
				
			||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
 | 
					 | 
				
			||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
 | 
					github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
 | 
				
			||||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
 | 
					 | 
				
			||||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
 | 
					github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
 | 
				
			||||||
github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
 | 
					github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
 | 
				
			||||||
github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw=
 | 
					github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw=
 | 
				
			||||||
| 
						 | 
					@ -722,8 +718,7 @@ github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b h1:Ha+kSIoTutf4ytlVw
 | 
				
			||||||
github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b/go.mod h1:3UsooRp7yW5/NJQBlXcTsAHOoykEhNUYXkQ3r6ehEEY=
 | 
					github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b/go.mod h1:3UsooRp7yW5/NJQBlXcTsAHOoykEhNUYXkQ3r6ehEEY=
 | 
				
			||||||
github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586 h1:/of8Z8taCPftShATouOrBVy6GaTTjgQd/VfNiZp/VXQ=
 | 
					github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586 h1:/of8Z8taCPftShATouOrBVy6GaTTjgQd/VfNiZp/VXQ=
 | 
				
			||||||
github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU=
 | 
					github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU=
 | 
				
			||||||
github.com/grafana/grafana-aws-sdk v0.25.1 h1:waJERJueqB1GldclAh5+tBcY9Ju9AOO9xYSJEnOAuf4=
 | 
					github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2 h1:CWT7mOBPUht9n7F/NiBQnEM05pFmCP3Z8CZPGCVC1tM=
 | 
				
			||||||
github.com/grafana/grafana-aws-sdk v0.25.1/go.mod h1:3zghFF6edrxn0d6k6X9HpGZXDH+VfA+MwD2Pc/9X0ec=
 | 
					 | 
				
			||||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2/go.mod h1:s8GLONgVh/svnSsO0Eo+OgXc/RZqozI5/0n+pNm3MEE=
 | 
					github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2/go.mod h1:s8GLONgVh/svnSsO0Eo+OgXc/RZqozI5/0n+pNm3MEE=
 | 
				
			||||||
github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs=
 | 
					github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs=
 | 
				
			||||||
github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE=
 | 
					github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE=
 | 
				
			||||||
| 
						 | 
					@ -732,22 +727,17 @@ github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240426134450-5fe9f7b9dfd4
 | 
				
			||||||
github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240426134450-5fe9f7b9dfd4/go.mod h1:UBDIuvdUGUI5fMDHDAl6yAVpFhfwl5ojMaw1N68775w=
 | 
					github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240426134450-5fe9f7b9dfd4/go.mod h1:UBDIuvdUGUI5fMDHDAl6yAVpFhfwl5ojMaw1N68775w=
 | 
				
			||||||
github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8 h1:pyWJN79uW8QHZiQRasHGLCEkXSr3k6HCjdr0J2jZ3rU=
 | 
					github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8 h1:pyWJN79uW8QHZiQRasHGLCEkXSr3k6HCjdr0J2jZ3rU=
 | 
				
			||||||
github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU=
 | 
					github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU=
 | 
				
			||||||
 | 
					github.com/grafana/grafana-plugin-sdk-go v0.228.0 h1:LlPqyB+RZTtDy8RVYD7iQVJW5A0gMoGSI/+Ykz8HebQ=
 | 
				
			||||||
 | 
					github.com/grafana/grafana-plugin-sdk-go v0.228.0/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU=
 | 
				
			||||||
github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo=
 | 
					github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo=
 | 
				
			||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
 | 
					github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
 | 
				
			||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
 | 
					github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
 | 
				
			||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
 | 
					github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
 | 
				
			||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
 | 
					github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
 | 
				
			||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
 | 
					 | 
				
			||||||
github.com/hamba/avro/v2 v2.17.2 h1:6PKpEWzJfNnvBgn7m2/8WYaDOUASxfDU+Jyb4ojDgFY=
 | 
					 | 
				
			||||||
github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo=
 | 
					github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo=
 | 
				
			||||||
github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
 | 
					 | 
				
			||||||
github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek=
 | 
					 | 
				
			||||||
github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU=
 | 
					 | 
				
			||||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 | 
					github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 | 
				
			||||||
github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o=
 | 
					github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o=
 | 
				
			||||||
github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 | 
					github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 | 
				
			||||||
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
 | 
					 | 
				
			||||||
github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw=
 | 
					 | 
				
			||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 | 
					github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 | 
				
			||||||
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
 | 
					github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
 | 
				
			||||||
github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ=
 | 
					github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ=
 | 
				
			||||||
| 
						 | 
					@ -801,11 +791,6 @@ github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
 | 
				
			||||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 | 
					github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 | 
				
			||||||
github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o=
 | 
					github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o=
 | 
				
			||||||
github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0=
 | 
					github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0=
 | 
				
			||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo=
 | 
					 | 
				
			||||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
 | 
					 | 
				
			||||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0=
 | 
					 | 
				
			||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
 | 
					 | 
				
			||||||
github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY=
 | 
					 | 
				
			||||||
github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4=
 | 
					github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4=
 | 
				
			||||||
github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I=
 | 
					github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I=
 | 
				
			||||||
github.com/kataras/golog v0.1.9 h1:vLvSDpP7kihFGKFAvBSofYo7qZNULYSHOH2D7rPTKJk=
 | 
					github.com/kataras/golog v0.1.9 h1:vLvSDpP7kihFGKFAvBSofYo7qZNULYSHOH2D7rPTKJk=
 | 
				
			||||||
| 
						 | 
					@ -820,13 +805,9 @@ github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY
 | 
				
			||||||
github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
 | 
					github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
 | 
				
			||||||
github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
 | 
					github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
 | 
				
			||||||
github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
 | 
					github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
 | 
				
			||||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
 | 
					 | 
				
			||||||
github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY=
 | 
					 | 
				
			||||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
 | 
					 | 
				
			||||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 | 
					github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 | 
				
			||||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 | 
					github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 | 
				
			||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 | 
					github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 | 
				
			||||||
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
 | 
					 | 
				
			||||||
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
 | 
					github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
 | 
				
			||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
 | 
					github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
 | 
				
			||||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 | 
					github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 | 
				
			||||||
| 
						 | 
					@ -1004,10 +985,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
 | 
				
			||||||
github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=
 | 
					github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=
 | 
				
			||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
 | 
					github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
 | 
				
			||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 | 
					github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 | 
				
			||||||
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
 | 
					 | 
				
			||||||
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
 | 
					github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
 | 
				
			||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 | 
					 | 
				
			||||||
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
 | 
					 | 
				
			||||||
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
 | 
					github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
 | 
				
			||||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
 | 
					github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
 | 
				
			||||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
 | 
					github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
 | 
				
			||||||
| 
						 | 
					@ -1021,13 +999,9 @@ github.com/tdewolff/minify/v2 v2.12.9 h1:dvn5MtmuQ/DFMwqf5j8QhEVpPX6fi3WGImhv8RU
 | 
				
			||||||
github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU=
 | 
					github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU=
 | 
				
			||||||
github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA=
 | 
					github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA=
 | 
				
			||||||
github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
 | 
					github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
 | 
				
			||||||
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
 | 
					 | 
				
			||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 | 
					github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 | 
				
			||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 | 
					 | 
				
			||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 | 
					github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 | 
				
			||||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
 | 
					 | 
				
			||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 | 
					github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 | 
				
			||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
 | 
					 | 
				
			||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 | 
					github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 | 
				
			||||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
 | 
					github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
 | 
				
			||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
 | 
					github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
 | 
				
			||||||
| 
						 | 
					@ -1037,7 +1011,6 @@ github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYm
 | 
				
			||||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
 | 
					github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
 | 
				
			||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
 | 
					github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
 | 
				
			||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
 | 
					github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
 | 
				
			||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 | 
					 | 
				
			||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 | 
					github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 | 
				
			||||||
github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o=
 | 
					github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o=
 | 
				
			||||||
github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
 | 
					github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
 | 
				
			||||||
| 
						 | 
					@ -1149,7 +1122,6 @@ go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
 | 
				
			||||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 | 
					go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 | 
				
			||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
 | 
					go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
 | 
				
			||||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
 | 
					go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
 | 
				
			||||||
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
 | 
					 | 
				
			||||||
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 | 
					golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
					golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
				
			||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 | 
					golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 | 
				
			||||||
| 
						 | 
					@ -1243,14 +1215,9 @@ gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI
 | 
				
			||||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 | 
					gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 | 
				
			||||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
 | 
					gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
 | 
				
			||||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
 | 
					gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
 | 
				
			||||||
gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs=
 | 
					 | 
				
			||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 | 
					 | 
				
			||||||
honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o=
 | 
					 | 
				
			||||||
k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 h1:WN8Lymy+dCTDHgn4vhUSNIB6U+0sDiv/c9Zdr0UeAnI=
 | 
					k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 h1:WN8Lymy+dCTDHgn4vhUSNIB6U+0sDiv/c9Zdr0UeAnI=
 | 
				
			||||||
k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6/go.mod h1:l0ukbPS0lwFxOzSq5ZqjutzF+5IL2TLp495PswRPSZk=
 | 
					k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6/go.mod h1:l0ukbPS0lwFxOzSq5ZqjutzF+5IL2TLp495PswRPSZk=
 | 
				
			||||||
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks=
 | 
					 | 
				
			||||||
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
 | 
					k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
 | 
				
			||||||
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo=
 | 
					 | 
				
			||||||
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=
 | 
					k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=
 | 
				
			||||||
k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA=
 | 
					k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA=
 | 
				
			||||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
 | 
					k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,15 +4,13 @@ import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"slices"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-jose/go-jose/v3/jwt"
 | 
						"github.com/go-jose/go-jose/v3/jwt"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	authlib "github.com/grafana/authlib/authn"
 | 
						authlib "github.com/grafana/authlib/authn"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/grafana/grafana/pkg/infra/log"
 | 
						"github.com/grafana/grafana/pkg/infra/log"
 | 
				
			||||||
 | 
						"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
 | 
				
			||||||
	"github.com/grafana/grafana/pkg/services/authn"
 | 
						"github.com/grafana/grafana/pkg/services/authn"
 | 
				
			||||||
	"github.com/grafana/grafana/pkg/services/login"
 | 
						"github.com/grafana/grafana/pkg/services/login"
 | 
				
			||||||
	"github.com/grafana/grafana/pkg/services/signingkeys"
 | 
						"github.com/grafana/grafana/pkg/services/signingkeys"
 | 
				
			||||||
| 
						 | 
					@ -22,10 +20,6 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ authn.Client = new(ExtendedJWT)
 | 
					var _ authn.Client = new(ExtendedJWT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					 | 
				
			||||||
	acceptedSigningMethods = []string{"RS256", "ES256"}
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	rfc9068ShortMediaType          = "at+jwt"
 | 
						rfc9068ShortMediaType          = "at+jwt"
 | 
				
			||||||
	extJWTAuthenticationHeaderName = "X-Access-Token"
 | 
						extJWTAuthenticationHeaderName = "X-Access-Token"
 | 
				
			||||||
| 
						 | 
					@ -34,7 +28,14 @@ const (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ProvideExtendedJWT(userService user.Service, cfg *setting.Cfg,
 | 
					func ProvideExtendedJWT(userService user.Service, cfg *setting.Cfg,
 | 
				
			||||||
	signingKeys signingkeys.Service) *ExtendedJWT {
 | 
						signingKeys signingkeys.Service) *ExtendedJWT {
 | 
				
			||||||
	verifier := authlib.NewVerifier[ExtendedJWTClaims](authlib.IDVerifierConfig{
 | 
						verifier := authlib.NewAccessTokenVerifier(authlib.VerifierConfig{
 | 
				
			||||||
 | 
							SigningKeysURL: cfg.ExtJWTAuth.JWKSUrl,
 | 
				
			||||||
 | 
							AllowedAudiences: []string{
 | 
				
			||||||
 | 
								cfg.ExtJWTAuth.ExpectAudience,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						idTokenVerifier := authlib.NewIDTokenVerifier(authlib.VerifierConfig{
 | 
				
			||||||
		SigningKeysURL: cfg.ExtJWTAuth.JWKSUrl,
 | 
							SigningKeysURL: cfg.ExtJWTAuth.JWKSUrl,
 | 
				
			||||||
		AllowedAudiences: []string{
 | 
							AllowedAudiences: []string{
 | 
				
			||||||
			cfg.ExtJWTAuth.ExpectAudience,
 | 
								cfg.ExtJWTAuth.ExpectAudience,
 | 
				
			||||||
| 
						 | 
					@ -46,7 +47,10 @@ func ProvideExtendedJWT(userService user.Service, cfg *setting.Cfg,
 | 
				
			||||||
		log:                 log.New(authn.ClientExtendedJWT),
 | 
							log:                 log.New(authn.ClientExtendedJWT),
 | 
				
			||||||
		userService:         userService,
 | 
							userService:         userService,
 | 
				
			||||||
		signingKeys:         signingKeys,
 | 
							signingKeys:         signingKeys,
 | 
				
			||||||
		verifier:    verifier,
 | 
							accessTokenVerifier: verifier,
 | 
				
			||||||
 | 
							namespaceMapper:     request.GetNamespaceMapper(cfg),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							idTokenVerifier: idTokenVerifier,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,31 +59,23 @@ type ExtendedJWT struct {
 | 
				
			||||||
	log                 log.Logger
 | 
						log                 log.Logger
 | 
				
			||||||
	userService         user.Service
 | 
						userService         user.Service
 | 
				
			||||||
	signingKeys         signingkeys.Service
 | 
						signingKeys         signingkeys.Service
 | 
				
			||||||
	verifier    authlib.Verifier[ExtendedJWTClaims]
 | 
						accessTokenVerifier authlib.Verifier[authlib.AccessTokenClaims]
 | 
				
			||||||
}
 | 
						idTokenVerifier     authlib.Verifier[authlib.IDTokenClaims]
 | 
				
			||||||
 | 
						namespaceMapper     request.NamespaceMapper
 | 
				
			||||||
type ExtendedJWTClaims struct {
 | 
					 | 
				
			||||||
	jwt.Claims
 | 
					 | 
				
			||||||
	// Access policy scopes
 | 
					 | 
				
			||||||
	Scopes []string `json:"scopes"`
 | 
					 | 
				
			||||||
	// Grafana roles
 | 
					 | 
				
			||||||
	Permissions []string `json:"permissions"`
 | 
					 | 
				
			||||||
	// On-behalf-of user
 | 
					 | 
				
			||||||
	DelegatedPermissions []string `json:"delegatedPermissions"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
 | 
					func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
 | 
				
			||||||
	jwtToken := s.retrieveAuthenticationToken(r.HTTPRequest)
 | 
						jwtToken := s.retrieveAuthenticationToken(r.HTTPRequest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	claims, err := s.verifyRFC9068Token(ctx, jwtToken, rfc9068ShortMediaType)
 | 
						claims, err := s.accessTokenVerifier.Verify(ctx, jwtToken)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		s.log.Error("Failed to verify JWT", "error", err)
 | 
							s.log.Error("Failed to verify access token", "error", err)
 | 
				
			||||||
		return nil, errJWTInvalid.Errorf("Failed to verify JWT: %w", err)
 | 
							return nil, errJWTInvalid.Errorf("Failed to verify access token: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	idToken := s.retrieveAuthorizationToken(r.HTTPRequest)
 | 
						idToken := s.retrieveAuthorizationToken(r.HTTPRequest)
 | 
				
			||||||
	if idToken != "" {
 | 
						if idToken != "" {
 | 
				
			||||||
		idTokenClaims, err := s.verifyRFC9068Token(ctx, idToken, "jwt")
 | 
							idTokenClaims, err := s.idTokenVerifier.Verify(ctx, idToken)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			s.log.Error("Failed to verify id token", "error", err)
 | 
								s.log.Error("Failed to verify id token", "error", err)
 | 
				
			||||||
			return nil, errJWTInvalid.Errorf("Failed to verify id token: %w", err)
 | 
								return nil, errJWTInvalid.Errorf("Failed to verify id token: %w", err)
 | 
				
			||||||
| 
						 | 
					@ -95,8 +91,18 @@ func (s *ExtendedJWT) IsEnabled() bool {
 | 
				
			||||||
	return s.cfg.ExtJWTAuth.Enabled
 | 
						return s.cfg.ExtJWTAuth.Enabled
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *ExtendedJWT) authenticateAsUser(idTokenClaims,
 | 
					func (s *ExtendedJWT) authenticateAsUser(idTokenClaims *authlib.Claims[authlib.IDTokenClaims],
 | 
				
			||||||
	accessTokenClaims *ExtendedJWTClaims) (*authn.Identity, error) {
 | 
						accessTokenClaims *authlib.Claims[authlib.AccessTokenClaims]) (*authn.Identity, error) {
 | 
				
			||||||
 | 
						// compare the incoming namespace claim against what namespaceMapper returns
 | 
				
			||||||
 | 
						if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); idTokenClaims.Rest.Namespace != allowedNamespace {
 | 
				
			||||||
 | 
							return nil, errJWTDisallowedNamespaceClaim
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// since id token claims can never have a wildcard ("*") namespace claim, the below comparison effectively
 | 
				
			||||||
 | 
						// disallows wildcard claims in access tokens here in Grafana (they are only meant for service layer)
 | 
				
			||||||
 | 
						if accessTokenClaims.Rest.Namespace != idTokenClaims.Rest.Namespace {
 | 
				
			||||||
 | 
							return nil, errJWTMismatchedNamespaceClaims.Errorf("id token namespace: %s, access token namespace: %s", idTokenClaims.Rest.Namespace, accessTokenClaims.Rest.Namespace)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Only allow access policies to impersonate
 | 
						// Only allow access policies to impersonate
 | 
				
			||||||
	if !strings.HasPrefix(accessTokenClaims.Subject, fmt.Sprintf("%s:", authn.NamespaceAccessPolicy)) {
 | 
						if !strings.HasPrefix(accessTokenClaims.Subject, fmt.Sprintf("%s:", authn.NamespaceAccessPolicy)) {
 | 
				
			||||||
		s.log.Error("Invalid subject", "subject", accessTokenClaims.Subject)
 | 
							s.log.Error("Invalid subject", "subject", accessTokenClaims.Subject)
 | 
				
			||||||
| 
						 | 
					@ -122,18 +128,23 @@ func (s *ExtendedJWT) authenticateAsUser(idTokenClaims,
 | 
				
			||||||
		ClientParams: authn.ClientParams{
 | 
							ClientParams: authn.ClientParams{
 | 
				
			||||||
			SyncPermissions: true,
 | 
								SyncPermissions: true,
 | 
				
			||||||
			FetchPermissionsParams: authn.FetchPermissionsParams{
 | 
								FetchPermissionsParams: authn.FetchPermissionsParams{
 | 
				
			||||||
				ActionsLookup: accessTokenClaims.DelegatedPermissions,
 | 
									ActionsLookup: accessTokenClaims.Rest.DelegatedPermissions,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			FetchSyncedUser: true,
 | 
								FetchSyncedUser: true,
 | 
				
			||||||
		}}, nil
 | 
							}}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *ExtendedJWT) authenticateAsService(claims *ExtendedJWTClaims) (*authn.Identity, error) {
 | 
					func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.AccessTokenClaims]) (*authn.Identity, error) {
 | 
				
			||||||
	if !strings.HasPrefix(claims.Subject, fmt.Sprintf("%s:", authn.NamespaceAccessPolicy)) {
 | 
						if !strings.HasPrefix(claims.Subject, fmt.Sprintf("%s:", authn.NamespaceAccessPolicy)) {
 | 
				
			||||||
		s.log.Error("Invalid subject", "subject", claims.Subject)
 | 
							s.log.Error("Invalid subject", "subject", claims.Subject)
 | 
				
			||||||
		return nil, errJWTInvalid.Errorf("Failed to parse sub: %s", "invalid subject format")
 | 
							return nil, errJWTInvalid.Errorf("Failed to parse sub: %s", "invalid subject format")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// same as asUser, disallows wildcard claims in access tokens here in Grafana (they are only meant for service layer)
 | 
				
			||||||
 | 
						if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); claims.Rest.Namespace != allowedNamespace {
 | 
				
			||||||
 | 
							return nil, errJWTDisallowedNamespaceClaim
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	id, err := authn.ParseNamespaceID(claims.Subject)
 | 
						id, err := authn.ParseNamespaceID(claims.Subject)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
| 
						 | 
					@ -147,7 +158,7 @@ func (s *ExtendedJWT) authenticateAsService(claims *ExtendedJWTClaims) (*authn.I
 | 
				
			||||||
		ClientParams: authn.ClientParams{
 | 
							ClientParams: authn.ClientParams{
 | 
				
			||||||
			SyncPermissions: true,
 | 
								SyncPermissions: true,
 | 
				
			||||||
			FetchPermissionsParams: authn.FetchPermissionsParams{
 | 
								FetchPermissionsParams: authn.FetchPermissionsParams{
 | 
				
			||||||
				Roles: claims.Permissions,
 | 
									Roles: claims.Rest.Permissions,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			FetchSyncedUser: false,
 | 
								FetchSyncedUser: false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					@ -202,59 +213,6 @@ func (s *ExtendedJWT) retrieveAuthorizationToken(httpRequest *http.Request) stri
 | 
				
			||||||
	return strings.TrimPrefix(jwtToken, "Bearer ")
 | 
						return strings.TrimPrefix(jwtToken, "Bearer ")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// verifyRFC9068Token verifies the token against the RFC 9068 specification.
 | 
					 | 
				
			||||||
func (s *ExtendedJWT) verifyRFC9068Token(ctx context.Context, rawToken string, typ string) (*ExtendedJWTClaims, error) {
 | 
					 | 
				
			||||||
	parsedToken, err := jwt.ParseSigned(rawToken)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("failed to parse JWT: %w", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(parsedToken.Headers) != 1 {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("only one header supported, got %d", len(parsedToken.Headers))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	parsedHeader := parsedToken.Headers[0]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	typeHeader := parsedHeader.ExtraHeaders["typ"]
 | 
					 | 
				
			||||||
	if typeHeader == nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("missing 'typ' field from the header")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	jwtType := strings.ToLower(typeHeader.(string))
 | 
					 | 
				
			||||||
	if !strings.EqualFold(jwtType, typ) {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("invalid JWT type: %s", jwtType)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !slices.Contains(acceptedSigningMethods, parsedHeader.Algorithm) {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("invalid algorithm: %s. Accepted algorithms: %s",
 | 
					 | 
				
			||||||
			parsedHeader.Algorithm, strings.Join(acceptedSigningMethods, ", "))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	keyID := parsedHeader.KeyID
 | 
					 | 
				
			||||||
	if keyID == "" {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("missing 'kid' field from the header")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	claims, err := s.verifier.Verify(ctx, rawToken)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("failed to verify JWT: %w", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if claims.Expiry == nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("missing 'exp' claim")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if claims.Subject == "" {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("missing 'sub' claim")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if claims.IssuedAt == nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("missing 'iat' claim")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &claims.Rest, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (s *ExtendedJWT) getDefaultOrgID() int64 {
 | 
					func (s *ExtendedJWT) getDefaultOrgID() int64 {
 | 
				
			||||||
	orgID := int64(1)
 | 
						orgID := int64(1)
 | 
				
			||||||
	if s.cfg.AutoAssignOrg && s.cfg.AutoAssignOrgId > 0 {
 | 
						if s.cfg.AutoAssignOrg && s.cfg.AutoAssignOrgId > 0 {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,9 +28,14 @@ import (
 | 
				
			||||||
	"github.com/grafana/grafana/pkg/setting"
 | 
						"github.com/grafana/grafana/pkg/setting"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type (
 | 
				
			||||||
 | 
						JWTAccessTokenClaims = authlib.Claims[authlib.AccessTokenClaims]
 | 
				
			||||||
 | 
						JWTIDTokenClaims     = authlib.Claims[authlib.IDTokenClaims]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	validPayload = ExtendedJWTClaims{
 | 
						validPayload = JWTAccessTokenClaims{
 | 
				
			||||||
		Claims: jwt.Claims{
 | 
							Claims: &jwt.Claims{
 | 
				
			||||||
			Issuer:   "http://localhost:3000",
 | 
								Issuer:   "http://localhost:3000",
 | 
				
			||||||
			Subject:  "access-policy:this-uid",
 | 
								Subject:  "access-policy:this-uid",
 | 
				
			||||||
			Audience: jwt.Audience{"http://localhost:3000"},
 | 
								Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
| 
						 | 
					@ -38,12 +43,15 @@ var (
 | 
				
			||||||
			Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
								Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
			IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
								IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
			Scopes:               []string{"profile", "groups"},
 | 
								Scopes:               []string{"profile", "groups"},
 | 
				
			||||||
			DelegatedPermissions: []string{"dashboards:create", "folders:read", "datasources:explore", "datasources.insights:read"},
 | 
								DelegatedPermissions: []string{"dashboards:create", "folders:read", "datasources:explore", "datasources.insights:read"},
 | 
				
			||||||
			Permissions:          []string{"fixed:folders:reader"},
 | 
								Permissions:          []string{"fixed:folders:reader"},
 | 
				
			||||||
 | 
								Namespace:            "default", // org ID of 1 is special and translates to default
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	validIDPayload = ExtendedJWTClaims{
 | 
						validIDPayload = JWTIDTokenClaims{
 | 
				
			||||||
		Claims: jwt.Claims{
 | 
							Claims: &jwt.Claims{
 | 
				
			||||||
			Issuer:   "http://localhost:3000",
 | 
								Issuer:   "http://localhost:3000",
 | 
				
			||||||
			Subject:  "user:2",
 | 
								Subject:  "user:2",
 | 
				
			||||||
			Audience: jwt.Audience{"http://localhost:3000"},
 | 
								Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
| 
						 | 
					@ -51,24 +59,61 @@ var (
 | 
				
			||||||
			Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
								Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
			IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
								IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		Scopes: []string{"profile", "groups"},
 | 
							Rest: authlib.IDTokenClaims{
 | 
				
			||||||
 | 
								AuthenticatedBy: "extended_jwt",
 | 
				
			||||||
 | 
								Namespace:       "default", // org ID of 1 is special and translates to default
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						validPayloadWildcardNamespace = JWTAccessTokenClaims{
 | 
				
			||||||
 | 
							Claims: &jwt.Claims{
 | 
				
			||||||
 | 
								Issuer:   "http://localhost:3000",
 | 
				
			||||||
 | 
								Subject:  "access-policy:this-uid",
 | 
				
			||||||
 | 
								Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
 | 
								ID:       "1234567890",
 | 
				
			||||||
 | 
								Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
 | 
								IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
 | 
								Namespace: "*",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mismatchingNamespaceIDPayload = JWTIDTokenClaims{
 | 
				
			||||||
 | 
							Claims: &jwt.Claims{
 | 
				
			||||||
 | 
								Issuer:   "http://localhost:3000",
 | 
				
			||||||
 | 
								Subject:  "user:2",
 | 
				
			||||||
 | 
								Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
 | 
								ID:       "1234567890",
 | 
				
			||||||
 | 
								Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
 | 
								IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Rest: authlib.IDTokenClaims{
 | 
				
			||||||
 | 
								AuthenticatedBy: "extended_jwt",
 | 
				
			||||||
 | 
								Namespace:       "org-2",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pk, _ = rsa.GenerateKey(rand.Reader, 4096)
 | 
						pk, _ = rsa.GenerateKey(rand.Reader, 4096)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type mockVerifier struct {
 | 
					var _ authlib.Verifier[authlib.IDTokenClaims] = &mockIDVerifier{}
 | 
				
			||||||
	Claims  []ExtendedJWTClaims
 | 
					
 | 
				
			||||||
 | 
					type mockIDVerifier struct {
 | 
				
			||||||
 | 
						Claims JWTIDTokenClaims
 | 
				
			||||||
	Error  error
 | 
						Error  error
 | 
				
			||||||
	counter int
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *mockVerifier) Verify(ctx context.Context, token string) (*authlib.Claims[ExtendedJWTClaims], error) {
 | 
					func (m *mockIDVerifier) Verify(ctx context.Context, token string) (*JWTIDTokenClaims, error) {
 | 
				
			||||||
	m.counter++
 | 
						return &m.Claims, m.Error
 | 
				
			||||||
	claims := m.Claims[m.counter-1]
 | 
					}
 | 
				
			||||||
	return &authlib.Claims[ExtendedJWTClaims]{
 | 
					
 | 
				
			||||||
		Claims: &claims.Claims,
 | 
					var _ authlib.Verifier[authlib.AccessTokenClaims] = &mockVerifier{}
 | 
				
			||||||
		Rest:   claims,
 | 
					
 | 
				
			||||||
	}, m.Error
 | 
					type mockVerifier struct {
 | 
				
			||||||
 | 
						Claims JWTAccessTokenClaims
 | 
				
			||||||
 | 
						Error  error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *mockVerifier) Verify(ctx context.Context, token string) (*JWTAccessTokenClaims, error) {
 | 
				
			||||||
 | 
						return &m.Claims, m.Error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestExtendedJWT_Test(t *testing.T) {
 | 
					func TestExtendedJWT_Test(t *testing.T) {
 | 
				
			||||||
| 
						 | 
					@ -93,13 +138,13 @@ func TestExtendedJWT_Test(t *testing.T) {
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name:           "should return true when Authorization header contains Bearer prefix",
 | 
								name:           "should return true when Authorization header contains Bearer prefix",
 | 
				
			||||||
			cfg:            nil,
 | 
								cfg:            nil,
 | 
				
			||||||
			authHeaderFunc: func() string { return "Bearer " + generateToken(validPayload, pk, jose.RS256, "at+jwt") },
 | 
								authHeaderFunc: func() string { return "Bearer " + generateToken(validPayload, pk, jose.RS256) },
 | 
				
			||||||
			want:           true,
 | 
								want:           true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name:           "should return true when Authorization header only contains the token",
 | 
								name:           "should return true when Authorization header only contains the token",
 | 
				
			||||||
			cfg:            nil,
 | 
								cfg:            nil,
 | 
				
			||||||
			authHeaderFunc: func() string { return generateToken(validPayload, pk, jose.RS256, "at+jwt") },
 | 
								authHeaderFunc: func() string { return generateToken(validPayload, pk, jose.RS256) },
 | 
				
			||||||
			want:           true,
 | 
								want:           true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
| 
						 | 
					@ -124,7 +169,7 @@ func TestExtendedJWT_Test(t *testing.T) {
 | 
				
			||||||
			authHeaderFunc: func() string {
 | 
								authHeaderFunc: func() string {
 | 
				
			||||||
				payload := validPayload
 | 
									payload := validPayload
 | 
				
			||||||
				payload.Issuer = "http://unknown-issuer"
 | 
									payload.Issuer = "http://unknown-issuer"
 | 
				
			||||||
				return generateToken(payload, pk, jose.RS256, "at+jwt")
 | 
									return generateToken(payload, pk, jose.RS256)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			want: false,
 | 
								want: false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					@ -152,8 +197,8 @@ func TestExtendedJWT_Test(t *testing.T) {
 | 
				
			||||||
func TestExtendedJWT_Authenticate(t *testing.T) {
 | 
					func TestExtendedJWT_Authenticate(t *testing.T) {
 | 
				
			||||||
	type testCase struct {
 | 
						type testCase struct {
 | 
				
			||||||
		name        string
 | 
							name        string
 | 
				
			||||||
		payload     ExtendedJWTClaims
 | 
							payload     *JWTAccessTokenClaims
 | 
				
			||||||
		idPayload   *ExtendedJWTClaims
 | 
							idPayload   *JWTIDTokenClaims
 | 
				
			||||||
		orgID       int64
 | 
							orgID       int64
 | 
				
			||||||
		want        *authn.Identity
 | 
							want        *authn.Identity
 | 
				
			||||||
		initTestEnv func(env *testEnv)
 | 
							initTestEnv func(env *testEnv)
 | 
				
			||||||
| 
						 | 
					@ -162,7 +207,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
 | 
				
			||||||
	testCases := []testCase{
 | 
						testCases := []testCase{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name:    "successful authentication as service",
 | 
								name:    "successful authentication as service",
 | 
				
			||||||
			payload: validPayload,
 | 
								payload: &validPayload,
 | 
				
			||||||
			orgID:   1,
 | 
								orgID:   1,
 | 
				
			||||||
			want: &authn.Identity{OrgID: 1, OrgName: "",
 | 
								want: &authn.Identity{OrgID: 1, OrgName: "",
 | 
				
			||||||
				OrgRoles: map[int64]roletype.RoleType(nil),
 | 
									OrgRoles: map[int64]roletype.RoleType(nil),
 | 
				
			||||||
| 
						 | 
					@ -182,7 +227,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name:      "successful authentication as user",
 | 
								name:      "successful authentication as user",
 | 
				
			||||||
			payload:   validPayload,
 | 
								payload:   &validPayload,
 | 
				
			||||||
			idPayload: &validIDPayload,
 | 
								idPayload: &validIDPayload,
 | 
				
			||||||
			orgID:     1,
 | 
								orgID:     1,
 | 
				
			||||||
			initTestEnv: func(env *testEnv) {
 | 
								initTestEnv: func(env *testEnv) {
 | 
				
			||||||
| 
						 | 
					@ -212,10 +257,44 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
 | 
				
			||||||
						Roles: []string(nil)}}, Permissions: map[int64]map[string][]string(nil), IDToken: ""},
 | 
											Roles: []string(nil)}}, Permissions: map[int64]map[string][]string(nil), IDToken: ""},
 | 
				
			||||||
			wantErr: nil,
 | 
								wantErr: nil,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "fail authentication as user when access token namespace claim doesn't match id token namespace",
 | 
				
			||||||
 | 
								payload:   &validPayload,
 | 
				
			||||||
 | 
								idPayload: &mismatchingNamespaceIDPayload,
 | 
				
			||||||
 | 
								orgID:     1,
 | 
				
			||||||
 | 
								initTestEnv: func(env *testEnv) {
 | 
				
			||||||
 | 
									env.userSvc.ExpectedSignedInUser = &user.SignedInUser{
 | 
				
			||||||
 | 
										UserID:  2,
 | 
				
			||||||
 | 
										OrgID:   1,
 | 
				
			||||||
 | 
										OrgRole: roletype.RoleAdmin,
 | 
				
			||||||
 | 
										Name:    "John Doe",
 | 
				
			||||||
 | 
										Email:   "johndoe@grafana.com",
 | 
				
			||||||
 | 
										Login:   "johndoe",
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: errJWTMismatchedNamespaceClaims.Errorf("id token namespace: %s, access token namespace: %s", mismatchingNamespaceIDPayload.Rest.Namespace, validPayload.Rest.Namespace),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "fail authentication as user when id token namespace claim doesn't match allowed namespace",
 | 
				
			||||||
 | 
								payload:   &validPayloadWildcardNamespace,
 | 
				
			||||||
 | 
								idPayload: &validIDPayload,
 | 
				
			||||||
 | 
								orgID:     1,
 | 
				
			||||||
 | 
								initTestEnv: func(env *testEnv) {
 | 
				
			||||||
 | 
									env.userSvc.ExpectedSignedInUser = &user.SignedInUser{
 | 
				
			||||||
 | 
										UserID:  2,
 | 
				
			||||||
 | 
										OrgID:   1,
 | 
				
			||||||
 | 
										OrgRole: roletype.RoleAdmin,
 | 
				
			||||||
 | 
										Name:    "John Doe",
 | 
				
			||||||
 | 
										Email:   "johndoe@grafana.com",
 | 
				
			||||||
 | 
										Login:   "johndoe",
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: errJWTDisallowedNamespaceClaim,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "should return error when the subject is not an access-policy",
 | 
								name: "should return error when the subject is not an access-policy",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Subject:  "user:2",
 | 
										Subject:  "user:2",
 | 
				
			||||||
					Audience: jwt.Audience{"http://localhost:3000"},
 | 
										Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
| 
						 | 
					@ -223,8 +302,10 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Permissions: []string{"fixed:folders:reader"},
 | 
										Permissions: []string{"fixed:folders:reader"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
			orgID:   1,
 | 
								orgID:   1,
 | 
				
			||||||
			want:    nil,
 | 
								want:    nil,
 | 
				
			||||||
			wantErr: errJWTInvalid.Errorf("Failed to parse sub: %s", "invalid subject format"),
 | 
								wantErr: errJWTInvalid.Errorf("Failed to parse sub: %s", "invalid subject format"),
 | 
				
			||||||
| 
						 | 
					@ -240,14 +321,15 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			validHTTPReq := &http.Request{
 | 
								validHTTPReq := &http.Request{
 | 
				
			||||||
				Header: map[string][]string{
 | 
									Header: map[string][]string{
 | 
				
			||||||
					"X-Access-Token": {generateToken(tc.payload, pk, jose.RS256, "at+jwt")},
 | 
										"X-Access-Token": {generateToken(*tc.payload, pk, jose.RS256)},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			env.s.verifier = &mockVerifier{Claims: []ExtendedJWTClaims{tc.payload}}
 | 
								env.s.accessTokenVerifier = &mockVerifier{Claims: *tc.payload}
 | 
				
			||||||
			if tc.idPayload != nil {
 | 
								if tc.idPayload != nil {
 | 
				
			||||||
				env.s.verifier = &mockVerifier{Claims: []ExtendedJWTClaims{tc.payload, *tc.idPayload}}
 | 
									env.s.accessTokenVerifier = &mockVerifier{Claims: *tc.payload}
 | 
				
			||||||
				validHTTPReq.Header.Add(extJWTAuthorizationHeaderName, generateToken(*tc.idPayload, pk, jose.RS256, "jwt"))
 | 
									env.s.idTokenVerifier = &mockIDVerifier{Claims: *tc.idPayload}
 | 
				
			||||||
 | 
									validHTTPReq.Header.Add(extJWTAuthorizationHeaderName, generateIDToken(*tc.idPayload, pk, jose.RS256))
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			id, err := env.s.Authenticate(context.Background(), &authn.Request{
 | 
								id, err := env.s.Authenticate(context.Background(), &authn.Request{
 | 
				
			||||||
| 
						 | 
					@ -269,42 +351,47 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
 | 
				
			||||||
func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) {
 | 
					func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) {
 | 
				
			||||||
	type testCase struct {
 | 
						type testCase struct {
 | 
				
			||||||
		name             string
 | 
							name             string
 | 
				
			||||||
		payload ExtendedJWTClaims
 | 
							payload          *JWTAccessTokenClaims
 | 
				
			||||||
 | 
							idPayload        *JWTIDTokenClaims
 | 
				
			||||||
		alg              jose.SignatureAlgorithm
 | 
							alg              jose.SignatureAlgorithm
 | 
				
			||||||
		typ     string
 | 
							generateWrongTyp bool
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	testCases := []testCase{
 | 
						testCases := []testCase{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "missing iss",
 | 
								name: "missing iss",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
										Subject:  "access-policy:this-uid",
 | 
				
			||||||
					Audience: jwt.Audience{"http://localhost:3000"},
 | 
										Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
					ID:       "1234567890",
 | 
										ID:       "1234567890",
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "missing expiry",
 | 
								name: "missing expiry",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
										Subject:  "access-policy:this-uid",
 | 
				
			||||||
					Audience: jwt.Audience{"http://localhost:3000"},
 | 
										Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
					ID:       "1234567890",
 | 
										ID:       "1234567890",
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "expired token",
 | 
								name: "expired token",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
										Subject:  "access-policy:this-uid",
 | 
				
			||||||
					Audience: jwt.Audience{"http://localhost:3000"},
 | 
										Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
| 
						 | 
					@ -312,26 +399,30 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) {
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "missing aud",
 | 
								name: "missing aud",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
										Subject:  "access-policy:this-uid",
 | 
				
			||||||
					ID:       "1234567890",
 | 
										ID:       "1234567890",
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "wrong aud",
 | 
								name: "wrong aud",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
										Subject:  "access-policy:this-uid",
 | 
				
			||||||
					Audience: jwt.Audience{"http://some-other-host:3000"},
 | 
										Audience: jwt.Audience{"http://some-other-host:3000"},
 | 
				
			||||||
| 
						 | 
					@ -339,54 +430,50 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) {
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name:             "wrong typ",
 | 
								name:             "wrong typ",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								idPayload:        &validIDPayload,
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
								generateWrongTyp: true,
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
					 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
					 | 
				
			||||||
					Audience: jwt.Audience{"http://some-other-host:3000"},
 | 
					 | 
				
			||||||
					ID:       "1234567890",
 | 
					 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
					 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				Scopes: []string{"profile", "groups"},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			typ: "jwt",
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "missing sub",
 | 
								name: "missing sub",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Audience: jwt.Audience{"http://localhost:3000"},
 | 
										Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
					ID:       "1234567890",
 | 
										ID:       "1234567890",
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "missing iat",
 | 
								name: "missing iat",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
										Subject:  "access-policy:this-uid",
 | 
				
			||||||
					Audience: jwt.Audience{"http://localhost:3000"},
 | 
										Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
					ID:       "1234567890",
 | 
										ID:       "1234567890",
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "iat later than current time",
 | 
								name: "iat later than current time",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
										Subject:  "access-policy:this-uid",
 | 
				
			||||||
					Audience: jwt.Audience{"http://localhost:3000"},
 | 
										Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
| 
						 | 
					@ -394,13 +481,15 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) {
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 2, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 2, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "unsupported alg",
 | 
								name: "unsupported alg",
 | 
				
			||||||
			payload: ExtendedJWTClaims{
 | 
								payload: &JWTAccessTokenClaims{
 | 
				
			||||||
				Claims: jwt.Claims{
 | 
									Claims: &jwt.Claims{
 | 
				
			||||||
					Issuer:   "http://localhost:3000",
 | 
										Issuer:   "http://localhost:3000",
 | 
				
			||||||
					Subject:  "access-policy:this-uid",
 | 
										Subject:  "access-policy:this-uid",
 | 
				
			||||||
					Audience: jwt.Audience{"http://localhost:3000"},
 | 
										Audience: jwt.Audience{"http://localhost:3000"},
 | 
				
			||||||
| 
						 | 
					@ -408,8 +497,10 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) {
 | 
				
			||||||
					Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
										Expiry:   jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
										IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									Rest: authlib.AccessTokenClaims{
 | 
				
			||||||
					Scopes: []string{"profile", "groups"},
 | 
										Scopes: []string{"profile", "groups"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
			alg: jose.RS384,
 | 
								alg: jose.RS384,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -421,8 +512,14 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) {
 | 
				
			||||||
			if tc.alg == "" {
 | 
								if tc.alg == "" {
 | 
				
			||||||
				tc.alg = jose.RS256
 | 
									tc.alg = jose.RS256
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			tokenToTest := generateToken(tc.payload, pk, tc.alg, "at+jwt")
 | 
					
 | 
				
			||||||
			_, err := env.s.verifyRFC9068Token(context.Background(), tokenToTest, rfc9068ShortMediaType)
 | 
								var tokenToTest string
 | 
				
			||||||
 | 
								if tc.generateWrongTyp {
 | 
				
			||||||
 | 
									tokenToTest = generateIDToken(*tc.idPayload, pk, tc.alg)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									tokenToTest = generateToken(*tc.payload, pk, tc.alg)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								_, err := env.s.accessTokenVerifier.Verify(context.Background(), tokenToTest)
 | 
				
			||||||
			require.Error(t, err)
 | 
								require.Error(t, err)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -431,6 +528,7 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) {
 | 
				
			||||||
func setupTestCtx(cfg *setting.Cfg) *testEnv {
 | 
					func setupTestCtx(cfg *setting.Cfg) *testEnv {
 | 
				
			||||||
	if cfg == nil {
 | 
						if cfg == nil {
 | 
				
			||||||
		cfg = &setting.Cfg{
 | 
							cfg = &setting.Cfg{
 | 
				
			||||||
 | 
								// default org set up by the authenticator is 1
 | 
				
			||||||
			ExtJWTAuth: setting.ExtJWTSettings{
 | 
								ExtJWTAuth: setting.ExtJWTSettings{
 | 
				
			||||||
				Enabled:        true,
 | 
									Enabled:        true,
 | 
				
			||||||
				ExpectIssuer:   "http://localhost:3000",
 | 
									ExpectIssuer:   "http://localhost:3000",
 | 
				
			||||||
| 
						 | 
					@ -459,10 +557,21 @@ type testEnv struct {
 | 
				
			||||||
	s       *ExtendedJWT
 | 
						s       *ExtendedJWT
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func generateToken(payload ExtendedJWTClaims, signingKey any, alg jose.SignatureAlgorithm, typ string) string {
 | 
					func generateToken(payload JWTAccessTokenClaims, signingKey any, alg jose.SignatureAlgorithm) string {
 | 
				
			||||||
	signer, _ := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: signingKey}, &jose.SignerOptions{
 | 
						signer, _ := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: signingKey}, &jose.SignerOptions{
 | 
				
			||||||
		ExtraHeaders: map[jose.HeaderKey]any{
 | 
							ExtraHeaders: map[jose.HeaderKey]any{
 | 
				
			||||||
			jose.HeaderType: typ,
 | 
								jose.HeaderType: authlib.TokenTypeAccess,
 | 
				
			||||||
 | 
								"kid":           "default",
 | 
				
			||||||
 | 
							}})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result, _ := jwt.Signed(signer).Claims(payload).CompactSerialize()
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func generateIDToken(payload JWTIDTokenClaims, signingKey any, alg jose.SignatureAlgorithm) string {
 | 
				
			||||||
 | 
						signer, _ := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: signingKey}, &jose.SignerOptions{
 | 
				
			||||||
 | 
							ExtraHeaders: map[jose.HeaderKey]any{
 | 
				
			||||||
 | 
								jose.HeaderType: authlib.TokenTypeID,
 | 
				
			||||||
			"kid":           "default",
 | 
								"kid":           "default",
 | 
				
			||||||
		}})
 | 
							}})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,10 @@ var (
 | 
				
			||||||
		"jwt.missing_claim", errutil.WithPublicMessage("Missing mandatory claim in JWT"))
 | 
							"jwt.missing_claim", errutil.WithPublicMessage("Missing mandatory claim in JWT"))
 | 
				
			||||||
	errJWTInvalidRole = errutil.Forbidden(
 | 
						errJWTInvalidRole = errutil.Forbidden(
 | 
				
			||||||
		"jwt.invalid_role", errutil.WithPublicMessage("Invalid Role in claim"))
 | 
							"jwt.invalid_role", errutil.WithPublicMessage("Invalid Role in claim"))
 | 
				
			||||||
 | 
						errJWTMismatchedNamespaceClaims = errutil.Unauthorized(
 | 
				
			||||||
 | 
							"jwt.namespace_mismatch", errutil.WithPublicMessage("Namespace claims didn't match between id token and access token"))
 | 
				
			||||||
 | 
						errJWTDisallowedNamespaceClaim = errutil.Unauthorized(
 | 
				
			||||||
 | 
							"jwt.namespace_mismatch", errutil.WithPublicMessage("Namespace claim doesn't allow access to requested namespace"))
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ProvideJWT(jwtService auth.JWTVerifierService, cfg *setting.Cfg) *JWT {
 | 
					func ProvideJWT(jwtService auth.JWTVerifierService, cfg *setting.Cfg) *JWT {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue