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/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/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/cuetsy v0.1.11 // @grafana/grafana-as-code | ||||
| 	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/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/authlib v0.0.0-20240328140636-a7388d0bac72 h1:lGEuhD/KhhN1OiPrvwQejl9Lg8MvaHdj3lHZNref4is= | ||||
| github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4= | ||||
| github.com/grafana/authlib v0.0.0-20240503035720-d1f918d6254a h1:9I4HHhjS634CWcsCS82wGvpkveypCzMe+2DMuzKoW5A= | ||||
| 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/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= | ||||
| 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/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= | ||||
|  | @ -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/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= | ||||
| 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/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/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= | ||||
| 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/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/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/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= | ||||
| 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/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/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/grafana-aws-sdk v0.25.1 h1:waJERJueqB1GldclAh5+tBcY9Ju9AOO9xYSJEnOAuf4= | ||||
| 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 h1:CWT7mOBPUht9n7F/NiBQnEM05pFmCP3Z8CZPGCVC1tM= | ||||
| 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.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.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.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/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-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/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/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.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= | ||||
| 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/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= | ||||
| 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/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/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/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= | ||||
| 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/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= | ||||
| 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/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/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= | ||||
| 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/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/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= | ||||
| 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/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/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= | ||||
| 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/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/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo= | ||||
| 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/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= | ||||
| 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/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= | ||||
| 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.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= | ||||
| 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/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= | ||||
| 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/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= | ||||
| 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/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| 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/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/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/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/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/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= | ||||
| k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= | ||||
|  |  | |||
|  | @ -4,15 +4,13 @@ import ( | |||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"slices" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/go-jose/go-jose/v3/jwt" | ||||
| 
 | ||||
| 	authlib "github.com/grafana/authlib/authn" | ||||
| 
 | ||||
| 	"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/login" | ||||
| 	"github.com/grafana/grafana/pkg/services/signingkeys" | ||||
|  | @ -22,10 +20,6 @@ import ( | |||
| 
 | ||||
| var _ authn.Client = new(ExtendedJWT) | ||||
| 
 | ||||
| var ( | ||||
| 	acceptedSigningMethods = []string{"RS256", "ES256"} | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	rfc9068ShortMediaType          = "at+jwt" | ||||
| 	extJWTAuthenticationHeaderName = "X-Access-Token" | ||||
|  | @ -34,7 +28,14 @@ const ( | |||
| 
 | ||||
| func ProvideExtendedJWT(userService user.Service, cfg *setting.Cfg, | ||||
| 	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, | ||||
| 		AllowedAudiences: []string{ | ||||
| 			cfg.ExtJWTAuth.ExpectAudience, | ||||
|  | @ -42,44 +43,39 @@ func ProvideExtendedJWT(userService user.Service, cfg *setting.Cfg, | |||
| 	}) | ||||
| 
 | ||||
| 	return &ExtendedJWT{ | ||||
| 		cfg:         cfg, | ||||
| 		log:         log.New(authn.ClientExtendedJWT), | ||||
| 		userService: userService, | ||||
| 		signingKeys: signingKeys, | ||||
| 		verifier:    verifier, | ||||
| 		cfg:                 cfg, | ||||
| 		log:                 log.New(authn.ClientExtendedJWT), | ||||
| 		userService:         userService, | ||||
| 		signingKeys:         signingKeys, | ||||
| 		accessTokenVerifier: verifier, | ||||
| 		namespaceMapper:     request.GetNamespaceMapper(cfg), | ||||
| 
 | ||||
| 		idTokenVerifier: idTokenVerifier, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type ExtendedJWT struct { | ||||
| 	cfg         *setting.Cfg | ||||
| 	log         log.Logger | ||||
| 	userService user.Service | ||||
| 	signingKeys signingkeys.Service | ||||
| 	verifier    authlib.Verifier[ExtendedJWTClaims] | ||||
| } | ||||
| 
 | ||||
| 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"` | ||||
| 	cfg                 *setting.Cfg | ||||
| 	log                 log.Logger | ||||
| 	userService         user.Service | ||||
| 	signingKeys         signingkeys.Service | ||||
| 	accessTokenVerifier authlib.Verifier[authlib.AccessTokenClaims] | ||||
| 	idTokenVerifier     authlib.Verifier[authlib.IDTokenClaims] | ||||
| 	namespaceMapper     request.NamespaceMapper | ||||
| } | ||||
| 
 | ||||
| func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { | ||||
| 	jwtToken := s.retrieveAuthenticationToken(r.HTTPRequest) | ||||
| 
 | ||||
| 	claims, err := s.verifyRFC9068Token(ctx, jwtToken, rfc9068ShortMediaType) | ||||
| 	claims, err := s.accessTokenVerifier.Verify(ctx, jwtToken) | ||||
| 	if err != nil { | ||||
| 		s.log.Error("Failed to verify JWT", "error", err) | ||||
| 		return nil, errJWTInvalid.Errorf("Failed to verify JWT: %w", err) | ||||
| 		s.log.Error("Failed to verify access token", "error", err) | ||||
| 		return nil, errJWTInvalid.Errorf("Failed to verify access token: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	idToken := s.retrieveAuthorizationToken(r.HTTPRequest) | ||||
| 	if idToken != "" { | ||||
| 		idTokenClaims, err := s.verifyRFC9068Token(ctx, idToken, "jwt") | ||||
| 		idTokenClaims, err := s.idTokenVerifier.Verify(ctx, idToken) | ||||
| 		if err != nil { | ||||
| 			s.log.Error("Failed to verify id token", "error", 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 | ||||
| } | ||||
| 
 | ||||
| func (s *ExtendedJWT) authenticateAsUser(idTokenClaims, | ||||
| 	accessTokenClaims *ExtendedJWTClaims) (*authn.Identity, error) { | ||||
| func (s *ExtendedJWT) authenticateAsUser(idTokenClaims *authlib.Claims[authlib.IDTokenClaims], | ||||
| 	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
 | ||||
| 	if !strings.HasPrefix(accessTokenClaims.Subject, fmt.Sprintf("%s:", authn.NamespaceAccessPolicy)) { | ||||
| 		s.log.Error("Invalid subject", "subject", accessTokenClaims.Subject) | ||||
|  | @ -122,18 +128,23 @@ func (s *ExtendedJWT) authenticateAsUser(idTokenClaims, | |||
| 		ClientParams: authn.ClientParams{ | ||||
| 			SyncPermissions: true, | ||||
| 			FetchPermissionsParams: authn.FetchPermissionsParams{ | ||||
| 				ActionsLookup: accessTokenClaims.DelegatedPermissions, | ||||
| 				ActionsLookup: accessTokenClaims.Rest.DelegatedPermissions, | ||||
| 			}, | ||||
| 			FetchSyncedUser: true, | ||||
| 		}}, 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)) { | ||||
| 		s.log.Error("Invalid subject", "subject", claims.Subject) | ||||
| 		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) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|  | @ -147,7 +158,7 @@ func (s *ExtendedJWT) authenticateAsService(claims *ExtendedJWTClaims) (*authn.I | |||
| 		ClientParams: authn.ClientParams{ | ||||
| 			SyncPermissions: true, | ||||
| 			FetchPermissionsParams: authn.FetchPermissionsParams{ | ||||
| 				Roles: claims.Permissions, | ||||
| 				Roles: claims.Rest.Permissions, | ||||
| 			}, | ||||
| 			FetchSyncedUser: false, | ||||
| 		}, | ||||
|  | @ -202,59 +213,6 @@ func (s *ExtendedJWT) retrieveAuthorizationToken(httpRequest *http.Request) stri | |||
| 	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 { | ||||
| 	orgID := int64(1) | ||||
| 	if s.cfg.AutoAssignOrg && s.cfg.AutoAssignOrgId > 0 { | ||||
|  |  | |||
|  | @ -28,9 +28,14 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/setting" | ||||
| ) | ||||
| 
 | ||||
| type ( | ||||
| 	JWTAccessTokenClaims = authlib.Claims[authlib.AccessTokenClaims] | ||||
| 	JWTIDTokenClaims     = authlib.Claims[authlib.IDTokenClaims] | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	validPayload = ExtendedJWTClaims{ | ||||
| 		Claims: jwt.Claims{ | ||||
| 	validPayload = JWTAccessTokenClaims{ | ||||
| 		Claims: &jwt.Claims{ | ||||
| 			Issuer:   "http://localhost:3000", | ||||
| 			Subject:  "access-policy:this-uid", | ||||
| 			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)), | ||||
| 			IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)), | ||||
| 		}, | ||||
| 		Scopes:               []string{"profile", "groups"}, | ||||
| 		DelegatedPermissions: []string{"dashboards:create", "folders:read", "datasources:explore", "datasources.insights:read"}, | ||||
| 		Permissions:          []string{"fixed:folders:reader"}, | ||||
| 		Rest: authlib.AccessTokenClaims{ | ||||
| 			Scopes:               []string{"profile", "groups"}, | ||||
| 			DelegatedPermissions: []string{"dashboards:create", "folders:read", "datasources:explore", "datasources.insights:read"}, | ||||
| 			Permissions:          []string{"fixed:folders:reader"}, | ||||
| 			Namespace:            "default", // org ID of 1 is special and translates to default
 | ||||
| 		}, | ||||
| 	} | ||||
| 	validIDPayload = ExtendedJWTClaims{ | ||||
| 		Claims: jwt.Claims{ | ||||
| 	validIDPayload = JWTIDTokenClaims{ | ||||
| 		Claims: &jwt.Claims{ | ||||
| 			Issuer:   "http://localhost:3000", | ||||
| 			Subject:  "user:2", | ||||
| 			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)), | ||||
| 			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) | ||||
| ) | ||||
| 
 | ||||
| type mockVerifier struct { | ||||
| 	Claims  []ExtendedJWTClaims | ||||
| 	Error   error | ||||
| 	counter int | ||||
| var _ authlib.Verifier[authlib.IDTokenClaims] = &mockIDVerifier{} | ||||
| 
 | ||||
| type mockIDVerifier struct { | ||||
| 	Claims JWTIDTokenClaims | ||||
| 	Error  error | ||||
| } | ||||
| 
 | ||||
| func (m *mockVerifier) Verify(ctx context.Context, token string) (*authlib.Claims[ExtendedJWTClaims], error) { | ||||
| 	m.counter++ | ||||
| 	claims := m.Claims[m.counter-1] | ||||
| 	return &authlib.Claims[ExtendedJWTClaims]{ | ||||
| 		Claims: &claims.Claims, | ||||
| 		Rest:   claims, | ||||
| 	}, m.Error | ||||
| func (m *mockIDVerifier) Verify(ctx context.Context, token string) (*JWTIDTokenClaims, error) { | ||||
| 	return &m.Claims, m.Error | ||||
| } | ||||
| 
 | ||||
| var _ authlib.Verifier[authlib.AccessTokenClaims] = &mockVerifier{} | ||||
| 
 | ||||
| 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) { | ||||
|  | @ -93,13 +138,13 @@ func TestExtendedJWT_Test(t *testing.T) { | |||
| 		{ | ||||
| 			name:           "should return true when Authorization header contains Bearer prefix", | ||||
| 			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, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "should return true when Authorization header only contains the token", | ||||
| 			cfg:            nil, | ||||
| 			authHeaderFunc: func() string { return generateToken(validPayload, pk, jose.RS256, "at+jwt") }, | ||||
| 			authHeaderFunc: func() string { return generateToken(validPayload, pk, jose.RS256) }, | ||||
| 			want:           true, | ||||
| 		}, | ||||
| 		{ | ||||
|  | @ -124,7 +169,7 @@ func TestExtendedJWT_Test(t *testing.T) { | |||
| 			authHeaderFunc: func() string { | ||||
| 				payload := validPayload | ||||
| 				payload.Issuer = "http://unknown-issuer" | ||||
| 				return generateToken(payload, pk, jose.RS256, "at+jwt") | ||||
| 				return generateToken(payload, pk, jose.RS256) | ||||
| 			}, | ||||
| 			want: false, | ||||
| 		}, | ||||
|  | @ -152,8 +197,8 @@ func TestExtendedJWT_Test(t *testing.T) { | |||
| func TestExtendedJWT_Authenticate(t *testing.T) { | ||||
| 	type testCase struct { | ||||
| 		name        string | ||||
| 		payload     ExtendedJWTClaims | ||||
| 		idPayload   *ExtendedJWTClaims | ||||
| 		payload     *JWTAccessTokenClaims | ||||
| 		idPayload   *JWTIDTokenClaims | ||||
| 		orgID       int64 | ||||
| 		want        *authn.Identity | ||||
| 		initTestEnv func(env *testEnv) | ||||
|  | @ -162,7 +207,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { | |||
| 	testCases := []testCase{ | ||||
| 		{ | ||||
| 			name:    "successful authentication as service", | ||||
| 			payload: validPayload, | ||||
| 			payload: &validPayload, | ||||
| 			orgID:   1, | ||||
| 			want: &authn.Identity{OrgID: 1, OrgName: "", | ||||
| 				OrgRoles: map[int64]roletype.RoleType(nil), | ||||
|  | @ -182,7 +227,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { | |||
| 		}, | ||||
| 		{ | ||||
| 			name:      "successful authentication as user", | ||||
| 			payload:   validPayload, | ||||
| 			payload:   &validPayload, | ||||
| 			idPayload: &validIDPayload, | ||||
| 			orgID:     1, | ||||
| 			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: ""}, | ||||
| 			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", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					Issuer:   "http://localhost:3000", | ||||
| 					Subject:  "user:2", | ||||
| 					Audience: jwt.Audience{"http://localhost:3000"}, | ||||
|  | @ -223,7 +302,9 @@ func TestExtendedJWT_Authenticate(t *testing.T) { | |||
| 					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)), | ||||
| 				}, | ||||
| 				Permissions: []string{"fixed:folders:reader"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Permissions: []string{"fixed:folders:reader"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			orgID:   1, | ||||
| 			want:    nil, | ||||
|  | @ -240,14 +321,15 @@ func TestExtendedJWT_Authenticate(t *testing.T) { | |||
| 
 | ||||
| 			validHTTPReq := &http.Request{ | ||||
| 				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 { | ||||
| 				env.s.verifier = &mockVerifier{Claims: []ExtendedJWTClaims{tc.payload, *tc.idPayload}} | ||||
| 				validHTTPReq.Header.Add(extJWTAuthorizationHeaderName, generateToken(*tc.idPayload, pk, jose.RS256, "jwt")) | ||||
| 				env.s.accessTokenVerifier = &mockVerifier{Claims: *tc.payload} | ||||
| 				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{ | ||||
|  | @ -268,43 +350,48 @@ func TestExtendedJWT_Authenticate(t *testing.T) { | |||
| // https://datatracker.ietf.org/doc/html/rfc9068#name-data-structure
 | ||||
| func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) { | ||||
| 	type testCase struct { | ||||
| 		name    string | ||||
| 		payload ExtendedJWTClaims | ||||
| 		alg     jose.SignatureAlgorithm | ||||
| 		typ     string | ||||
| 		name             string | ||||
| 		payload          *JWTAccessTokenClaims | ||||
| 		idPayload        *JWTIDTokenClaims | ||||
| 		alg              jose.SignatureAlgorithm | ||||
| 		generateWrongTyp bool | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []testCase{ | ||||
| 		{ | ||||
| 			name: "missing iss", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					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)), | ||||
| 				}, | ||||
| 				Scopes: []string{"profile", "groups"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "missing expiry", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					Issuer:   "http://localhost:3000", | ||||
| 					Subject:  "access-policy:this-uid", | ||||
| 					Audience: jwt.Audience{"http://localhost:3000"}, | ||||
| 					ID:       "1234567890", | ||||
| 					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)), | ||||
| 				}, | ||||
| 				Scopes: []string{"profile", "groups"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "expired token", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					Issuer:   "http://localhost:3000", | ||||
| 					Subject:  "access-policy:this-uid", | ||||
| 					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)), | ||||
| 					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)), | ||||
| 				}, | ||||
| 				Scopes: []string{"profile", "groups"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "missing aud", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					Issuer:   "http://localhost:3000", | ||||
| 					Subject:  "access-policy:this-uid", | ||||
| 					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"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "wrong aud", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					Issuer:   "http://localhost:3000", | ||||
| 					Subject:  "access-policy:this-uid", | ||||
| 					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)), | ||||
| 					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)), | ||||
| 				}, | ||||
| 				Scopes: []string{"profile", "groups"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "wrong typ", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 					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:             "wrong typ", | ||||
| 			idPayload:        &validIDPayload, | ||||
| 			generateWrongTyp: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "missing sub", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					Issuer:   "http://localhost:3000", | ||||
| 					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)), | ||||
| 				}, | ||||
| 				Scopes: []string{"profile", "groups"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "missing iat", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &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)), | ||||
| 				}, | ||||
| 				Scopes: []string{"profile", "groups"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "iat later than current time", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					Issuer:   "http://localhost:3000", | ||||
| 					Subject:  "access-policy:this-uid", | ||||
| 					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)), | ||||
| 					IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 2, 0, 0, time.UTC)), | ||||
| 				}, | ||||
| 				Scopes: []string{"profile", "groups"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "unsupported alg", | ||||
| 			payload: ExtendedJWTClaims{ | ||||
| 				Claims: jwt.Claims{ | ||||
| 			payload: &JWTAccessTokenClaims{ | ||||
| 				Claims: &jwt.Claims{ | ||||
| 					Issuer:   "http://localhost:3000", | ||||
| 					Subject:  "access-policy:this-uid", | ||||
| 					Audience: jwt.Audience{"http://localhost:3000"}, | ||||
|  | @ -408,7 +497,9 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) { | |||
| 					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"}, | ||||
| 				Rest: authlib.AccessTokenClaims{ | ||||
| 					Scopes: []string{"profile", "groups"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			alg: jose.RS384, | ||||
| 		}, | ||||
|  | @ -421,8 +512,14 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) { | |||
| 			if tc.alg == "" { | ||||
| 				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) | ||||
| 		}) | ||||
| 	} | ||||
|  | @ -431,6 +528,7 @@ func TestVerifyRFC9068TokenFailureScenarios(t *testing.T) { | |||
| func setupTestCtx(cfg *setting.Cfg) *testEnv { | ||||
| 	if cfg == nil { | ||||
| 		cfg = &setting.Cfg{ | ||||
| 			// default org set up by the authenticator is 1
 | ||||
| 			ExtJWTAuth: setting.ExtJWTSettings{ | ||||
| 				Enabled:        true, | ||||
| 				ExpectIssuer:   "http://localhost:3000", | ||||
|  | @ -459,10 +557,21 @@ type testEnv struct { | |||
| 	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{ | ||||
| 		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", | ||||
| 		}}) | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,6 +27,10 @@ var ( | |||
| 		"jwt.missing_claim", errutil.WithPublicMessage("Missing mandatory claim in JWT")) | ||||
| 	errJWTInvalidRole = errutil.Forbidden( | ||||
| 		"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 { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue