oidc_test.go 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package httpd
  15. import (
  16. "bytes"
  17. "context"
  18. "encoding/json"
  19. "fmt"
  20. "io/fs"
  21. "net/http"
  22. "net/http/httptest"
  23. "net/url"
  24. "os"
  25. "path/filepath"
  26. "reflect"
  27. "runtime"
  28. "testing"
  29. "time"
  30. "unsafe"
  31. "github.com/coreos/go-oidc/v3/oidc"
  32. "github.com/go-chi/jwtauth/v5"
  33. "github.com/lestrrat-go/jwx/v2/jwa"
  34. "github.com/rs/xid"
  35. "github.com/sftpgo/sdk"
  36. "github.com/stretchr/testify/assert"
  37. "github.com/stretchr/testify/require"
  38. "golang.org/x/oauth2"
  39. "github.com/drakkan/sftpgo/v2/internal/common"
  40. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  41. "github.com/drakkan/sftpgo/v2/internal/kms"
  42. "github.com/drakkan/sftpgo/v2/internal/util"
  43. "github.com/drakkan/sftpgo/v2/internal/vfs"
  44. )
  45. const (
  46. oidcMockAddr = "127.0.0.1:11111"
  47. )
  48. type mockTokenSource struct {
  49. token *oauth2.Token
  50. err error
  51. }
  52. func (t *mockTokenSource) Token() (*oauth2.Token, error) {
  53. return t.token, t.err
  54. }
  55. type mockOAuth2Config struct {
  56. tokenSource *mockTokenSource
  57. authCodeURL string
  58. token *oauth2.Token
  59. err error
  60. }
  61. func (c *mockOAuth2Config) AuthCodeURL(_ string, _ ...oauth2.AuthCodeOption) string {
  62. return c.authCodeURL
  63. }
  64. func (c *mockOAuth2Config) Exchange(_ context.Context, _ string, _ ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
  65. return c.token, c.err
  66. }
  67. func (c *mockOAuth2Config) TokenSource(_ context.Context, _ *oauth2.Token) oauth2.TokenSource {
  68. return c.tokenSource
  69. }
  70. type mockOIDCVerifier struct {
  71. token *oidc.IDToken
  72. err error
  73. }
  74. func (v *mockOIDCVerifier) Verify(_ context.Context, _ string) (*oidc.IDToken, error) {
  75. return v.token, v.err
  76. }
  77. // hack because the field is unexported
  78. func setIDTokenClaims(idToken *oidc.IDToken, claims []byte) {
  79. pointerVal := reflect.ValueOf(idToken)
  80. val := reflect.Indirect(pointerVal)
  81. member := val.FieldByName("claims")
  82. ptr := unsafe.Pointer(member.UnsafeAddr())
  83. realPtr := (*[]byte)(ptr)
  84. *realPtr = claims
  85. }
  86. func TestOIDCInitialization(t *testing.T) {
  87. config := OIDC{}
  88. err := config.initialize()
  89. assert.NoError(t, err)
  90. secret := "jRsmE0SWnuZjP7djBqNq0mrf8QN77j2c"
  91. config = OIDC{
  92. ClientID: "sftpgo-client",
  93. ClientSecret: util.GenerateUniqueID(),
  94. ConfigURL: fmt.Sprintf("http://%v/", oidcMockAddr),
  95. RedirectBaseURL: "http://127.0.0.1:8081/",
  96. UsernameField: "preferred_username",
  97. RoleField: "sftpgo_role",
  98. }
  99. err = config.initialize()
  100. if assert.Error(t, err) {
  101. assert.Contains(t, err.Error(), "oidc: required scope \"openid\" is not set")
  102. }
  103. config.Scopes = []string{oidc.ScopeOpenID}
  104. config.ClientSecretFile = "missing file"
  105. err = config.initialize()
  106. assert.ErrorIs(t, err, fs.ErrNotExist)
  107. secretFile := filepath.Join(os.TempDir(), util.GenerateUniqueID())
  108. defer os.Remove(secretFile)
  109. err = os.WriteFile(secretFile, []byte(secret), 0600)
  110. assert.NoError(t, err)
  111. config.ClientSecretFile = secretFile
  112. err = config.initialize()
  113. if assert.Error(t, err) {
  114. assert.Contains(t, err.Error(), "oidc: unable to initialize provider")
  115. }
  116. assert.Equal(t, secret, config.ClientSecret)
  117. config.ConfigURL = fmt.Sprintf("http://%v/auth/realms/sftpgo", oidcMockAddr)
  118. err = config.initialize()
  119. assert.NoError(t, err)
  120. assert.Equal(t, "http://127.0.0.1:8081"+webOIDCRedirectPath, config.getRedirectURL())
  121. }
  122. func TestOIDCLoginLogout(t *testing.T) {
  123. oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
  124. require.True(t, ok)
  125. server := getTestOIDCServer()
  126. err := server.binding.OIDC.initialize()
  127. assert.NoError(t, err)
  128. server.initializeRouter()
  129. rr := httptest.NewRecorder()
  130. r, err := http.NewRequest(http.MethodGet, webOIDCRedirectPath, nil)
  131. assert.NoError(t, err)
  132. server.router.ServeHTTP(rr, r)
  133. assert.Equal(t, http.StatusBadRequest, rr.Code)
  134. assert.Contains(t, rr.Body.String(), util.I18nInvalidAuth)
  135. expiredAuthReq := oidcPendingAuth{
  136. State: xid.New().String(),
  137. Nonce: xid.New().String(),
  138. Audience: tokenAudienceWebClient,
  139. IssuedAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-10 * time.Minute)),
  140. }
  141. oidcMgr.addPendingAuth(expiredAuthReq)
  142. rr = httptest.NewRecorder()
  143. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+expiredAuthReq.State, nil)
  144. assert.NoError(t, err)
  145. server.router.ServeHTTP(rr, r)
  146. assert.Equal(t, http.StatusBadRequest, rr.Code)
  147. assert.Contains(t, rr.Body.String(), util.I18nInvalidAuth)
  148. oidcMgr.removePendingAuth(expiredAuthReq.State)
  149. server.binding.OIDC.oauth2Config = &mockOAuth2Config{
  150. tokenSource: &mockTokenSource{},
  151. authCodeURL: webOIDCRedirectPath,
  152. err: common.ErrGenericFailure,
  153. }
  154. server.binding.OIDC.verifier = &mockOIDCVerifier{
  155. err: common.ErrGenericFailure,
  156. }
  157. rr = httptest.NewRecorder()
  158. r, err = http.NewRequest(http.MethodGet, webAdminOIDCLoginPath, nil)
  159. assert.NoError(t, err)
  160. server.router.ServeHTTP(rr, r)
  161. assert.Equal(t, http.StatusFound, rr.Code)
  162. assert.Equal(t, webOIDCRedirectPath, rr.Header().Get("Location"))
  163. require.Len(t, oidcMgr.pendingAuths, 1)
  164. var state string
  165. for k := range oidcMgr.pendingAuths {
  166. state = k
  167. }
  168. rr = httptest.NewRecorder()
  169. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+state, nil)
  170. assert.NoError(t, err)
  171. server.router.ServeHTTP(rr, r)
  172. assert.Equal(t, http.StatusFound, rr.Code)
  173. assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
  174. require.Len(t, oidcMgr.pendingAuths, 0)
  175. rr = httptest.NewRecorder()
  176. r, err = http.NewRequest(http.MethodGet, webAdminLoginPath, nil)
  177. assert.NoError(t, err)
  178. server.router.ServeHTTP(rr, r)
  179. assert.Equal(t, http.StatusOK, rr.Code)
  180. // now the same for the web client
  181. rr = httptest.NewRecorder()
  182. r, err = http.NewRequest(http.MethodGet, webClientOIDCLoginPath, nil)
  183. assert.NoError(t, err)
  184. server.router.ServeHTTP(rr, r)
  185. assert.Equal(t, http.StatusFound, rr.Code)
  186. assert.Equal(t, webOIDCRedirectPath, rr.Header().Get("Location"))
  187. require.Len(t, oidcMgr.pendingAuths, 1)
  188. for k := range oidcMgr.pendingAuths {
  189. state = k
  190. }
  191. rr = httptest.NewRecorder()
  192. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+state, nil)
  193. assert.NoError(t, err)
  194. server.router.ServeHTTP(rr, r)
  195. assert.Equal(t, http.StatusFound, rr.Code)
  196. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  197. require.Len(t, oidcMgr.pendingAuths, 0)
  198. rr = httptest.NewRecorder()
  199. r, err = http.NewRequest(http.MethodGet, webClientLoginPath, nil)
  200. assert.NoError(t, err)
  201. server.router.ServeHTTP(rr, r)
  202. assert.Equal(t, http.StatusOK, rr.Code)
  203. // now return an OAuth2 token without the id_token
  204. server.binding.OIDC.oauth2Config = &mockOAuth2Config{
  205. tokenSource: &mockTokenSource{},
  206. authCodeURL: webOIDCRedirectPath,
  207. token: &oauth2.Token{
  208. AccessToken: "123",
  209. Expiry: time.Now().Add(5 * time.Minute),
  210. },
  211. err: nil,
  212. }
  213. authReq := newOIDCPendingAuth(tokenAudienceWebClient)
  214. oidcMgr.addPendingAuth(authReq)
  215. rr = httptest.NewRecorder()
  216. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  217. assert.NoError(t, err)
  218. server.router.ServeHTTP(rr, r)
  219. assert.Equal(t, http.StatusFound, rr.Code)
  220. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  221. require.Len(t, oidcMgr.pendingAuths, 0)
  222. // now fail to verify the id token
  223. token := &oauth2.Token{
  224. AccessToken: "123",
  225. Expiry: time.Now().Add(5 * time.Minute),
  226. }
  227. token = token.WithExtra(map[string]any{
  228. "id_token": "id_token_val",
  229. })
  230. server.binding.OIDC.oauth2Config = &mockOAuth2Config{
  231. tokenSource: &mockTokenSource{},
  232. authCodeURL: webOIDCRedirectPath,
  233. token: token,
  234. err: nil,
  235. }
  236. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  237. oidcMgr.addPendingAuth(authReq)
  238. rr = httptest.NewRecorder()
  239. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  240. assert.NoError(t, err)
  241. server.router.ServeHTTP(rr, r)
  242. assert.Equal(t, http.StatusFound, rr.Code)
  243. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  244. require.Len(t, oidcMgr.pendingAuths, 0)
  245. // id token nonce does not match
  246. server.binding.OIDC.verifier = &mockOIDCVerifier{
  247. err: nil,
  248. token: &oidc.IDToken{},
  249. }
  250. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  251. oidcMgr.addPendingAuth(authReq)
  252. rr = httptest.NewRecorder()
  253. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  254. assert.NoError(t, err)
  255. server.router.ServeHTTP(rr, r)
  256. assert.Equal(t, http.StatusFound, rr.Code)
  257. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  258. require.Len(t, oidcMgr.pendingAuths, 0)
  259. // null id token claims
  260. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  261. oidcMgr.addPendingAuth(authReq)
  262. server.binding.OIDC.verifier = &mockOIDCVerifier{
  263. err: nil,
  264. token: &oidc.IDToken{
  265. Nonce: authReq.Nonce,
  266. },
  267. }
  268. rr = httptest.NewRecorder()
  269. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  270. assert.NoError(t, err)
  271. server.router.ServeHTTP(rr, r)
  272. assert.Equal(t, http.StatusFound, rr.Code)
  273. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  274. require.Len(t, oidcMgr.pendingAuths, 0)
  275. // invalid id token claims: no username
  276. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  277. oidcMgr.addPendingAuth(authReq)
  278. idToken := &oidc.IDToken{
  279. Nonce: authReq.Nonce,
  280. Expiry: time.Now().Add(5 * time.Minute),
  281. }
  282. setIDTokenClaims(idToken, []byte(`{"aud": "my_client_id"}`))
  283. server.binding.OIDC.verifier = &mockOIDCVerifier{
  284. err: nil,
  285. token: idToken,
  286. }
  287. rr = httptest.NewRecorder()
  288. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  289. assert.NoError(t, err)
  290. server.router.ServeHTTP(rr, r)
  291. assert.Equal(t, http.StatusFound, rr.Code)
  292. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  293. require.Len(t, oidcMgr.pendingAuths, 0)
  294. // invalid id token clamims: username not a string
  295. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  296. oidcMgr.addPendingAuth(authReq)
  297. idToken = &oidc.IDToken{
  298. Nonce: authReq.Nonce,
  299. Expiry: time.Now().Add(5 * time.Minute),
  300. }
  301. setIDTokenClaims(idToken, []byte(`{"aud": "my_client_id","preferred_username": 1}`))
  302. server.binding.OIDC.verifier = &mockOIDCVerifier{
  303. err: nil,
  304. token: idToken,
  305. }
  306. rr = httptest.NewRecorder()
  307. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  308. assert.NoError(t, err)
  309. server.router.ServeHTTP(rr, r)
  310. assert.Equal(t, http.StatusFound, rr.Code)
  311. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  312. require.Len(t, oidcMgr.pendingAuths, 0)
  313. // invalid audience
  314. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  315. oidcMgr.addPendingAuth(authReq)
  316. idToken = &oidc.IDToken{
  317. Nonce: authReq.Nonce,
  318. Expiry: time.Now().Add(5 * time.Minute),
  319. }
  320. setIDTokenClaims(idToken, []byte(`{"preferred_username":"test","sftpgo_role":"admin"}`))
  321. server.binding.OIDC.verifier = &mockOIDCVerifier{
  322. err: nil,
  323. token: idToken,
  324. }
  325. rr = httptest.NewRecorder()
  326. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  327. assert.NoError(t, err)
  328. server.router.ServeHTTP(rr, r)
  329. assert.Equal(t, http.StatusFound, rr.Code)
  330. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  331. require.Len(t, oidcMgr.pendingAuths, 0)
  332. // invalid audience
  333. authReq = newOIDCPendingAuth(tokenAudienceWebAdmin)
  334. oidcMgr.addPendingAuth(authReq)
  335. idToken = &oidc.IDToken{
  336. Nonce: authReq.Nonce,
  337. Expiry: time.Now().Add(5 * time.Minute),
  338. }
  339. setIDTokenClaims(idToken, []byte(`{"preferred_username":"test"}`))
  340. server.binding.OIDC.verifier = &mockOIDCVerifier{
  341. err: nil,
  342. token: idToken,
  343. }
  344. rr = httptest.NewRecorder()
  345. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  346. assert.NoError(t, err)
  347. server.router.ServeHTTP(rr, r)
  348. assert.Equal(t, http.StatusFound, rr.Code)
  349. assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
  350. require.Len(t, oidcMgr.pendingAuths, 0)
  351. // mapped user not found
  352. authReq = newOIDCPendingAuth(tokenAudienceWebAdmin)
  353. oidcMgr.addPendingAuth(authReq)
  354. idToken = &oidc.IDToken{
  355. Nonce: authReq.Nonce,
  356. Expiry: time.Now().Add(5 * time.Minute),
  357. }
  358. setIDTokenClaims(idToken, []byte(`{"preferred_username":"test","sftpgo_role":"admin"}`))
  359. server.binding.OIDC.verifier = &mockOIDCVerifier{
  360. err: nil,
  361. token: idToken,
  362. }
  363. rr = httptest.NewRecorder()
  364. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  365. assert.NoError(t, err)
  366. server.router.ServeHTTP(rr, r)
  367. assert.Equal(t, http.StatusFound, rr.Code)
  368. assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
  369. require.Len(t, oidcMgr.pendingAuths, 0)
  370. // admin login ok
  371. authReq = newOIDCPendingAuth(tokenAudienceWebAdmin)
  372. oidcMgr.addPendingAuth(authReq)
  373. idToken = &oidc.IDToken{
  374. Nonce: authReq.Nonce,
  375. Expiry: time.Now().Add(5 * time.Minute),
  376. }
  377. setIDTokenClaims(idToken, []byte(`{"preferred_username":"admin","sftpgo_role":"admin","sid":"sid123"}`))
  378. server.binding.OIDC.verifier = &mockOIDCVerifier{
  379. err: nil,
  380. token: idToken,
  381. }
  382. rr = httptest.NewRecorder()
  383. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  384. assert.NoError(t, err)
  385. server.router.ServeHTTP(rr, r)
  386. assert.Equal(t, http.StatusFound, rr.Code)
  387. assert.Equal(t, webUsersPath, rr.Header().Get("Location"))
  388. require.Len(t, oidcMgr.pendingAuths, 0)
  389. require.Len(t, oidcMgr.tokens, 1)
  390. // admin profile is not available
  391. var tokenCookie string
  392. for k := range oidcMgr.tokens {
  393. tokenCookie = k
  394. }
  395. oidcToken, err := oidcMgr.getToken(tokenCookie)
  396. assert.NoError(t, err)
  397. assert.Equal(t, "sid123", oidcToken.SessionID)
  398. assert.True(t, oidcToken.isAdmin())
  399. assert.False(t, oidcToken.isExpired())
  400. rr = httptest.NewRecorder()
  401. r, err = http.NewRequest(http.MethodGet, webAdminProfilePath, nil)
  402. assert.NoError(t, err)
  403. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  404. server.router.ServeHTTP(rr, r)
  405. assert.Equal(t, http.StatusForbidden, rr.Code)
  406. // the admin can access the allowed pages
  407. rr = httptest.NewRecorder()
  408. r, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
  409. assert.NoError(t, err)
  410. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  411. server.router.ServeHTTP(rr, r)
  412. assert.Equal(t, http.StatusOK, rr.Code)
  413. // try with an invalid cookie
  414. rr = httptest.NewRecorder()
  415. r, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
  416. assert.NoError(t, err)
  417. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, xid.New().String()))
  418. server.router.ServeHTTP(rr, r)
  419. assert.Equal(t, http.StatusFound, rr.Code)
  420. assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
  421. // Web Client is not available with an admin token
  422. rr = httptest.NewRecorder()
  423. r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
  424. assert.NoError(t, err)
  425. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  426. server.router.ServeHTTP(rr, r)
  427. assert.Equal(t, http.StatusFound, rr.Code)
  428. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  429. // logout the admin user
  430. rr = httptest.NewRecorder()
  431. r, err = http.NewRequest(http.MethodGet, webLogoutPath, nil)
  432. assert.NoError(t, err)
  433. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  434. server.router.ServeHTTP(rr, r)
  435. assert.Equal(t, http.StatusFound, rr.Code)
  436. assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
  437. require.Len(t, oidcMgr.pendingAuths, 0)
  438. require.Len(t, oidcMgr.tokens, 0)
  439. // now login and logout a user
  440. username := "test_oidc_user"
  441. user := dataprovider.User{
  442. BaseUser: sdk.BaseUser{
  443. Username: username,
  444. Password: "pwd",
  445. HomeDir: filepath.Join(os.TempDir(), username),
  446. Status: 1,
  447. Permissions: map[string][]string{
  448. "/": {dataprovider.PermAny},
  449. },
  450. },
  451. Filters: dataprovider.UserFilters{
  452. BaseUserFilters: sdk.BaseUserFilters{
  453. WebClient: []string{sdk.WebClientSharesDisabled},
  454. },
  455. },
  456. }
  457. err = dataprovider.AddUser(&user, "", "", "")
  458. assert.NoError(t, err)
  459. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  460. oidcMgr.addPendingAuth(authReq)
  461. idToken = &oidc.IDToken{
  462. Nonce: authReq.Nonce,
  463. Expiry: time.Now().Add(5 * time.Minute),
  464. }
  465. setIDTokenClaims(idToken, []byte(`{"preferred_username":"test_oidc_user"}`))
  466. server.binding.OIDC.verifier = &mockOIDCVerifier{
  467. err: nil,
  468. token: idToken,
  469. }
  470. rr = httptest.NewRecorder()
  471. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  472. assert.NoError(t, err)
  473. server.router.ServeHTTP(rr, r)
  474. assert.Equal(t, http.StatusFound, rr.Code)
  475. assert.Equal(t, webClientFilesPath, rr.Header().Get("Location"))
  476. require.Len(t, oidcMgr.pendingAuths, 0)
  477. require.Len(t, oidcMgr.tokens, 1)
  478. // user profile is not available
  479. for k := range oidcMgr.tokens {
  480. tokenCookie = k
  481. }
  482. oidcToken, err = oidcMgr.getToken(tokenCookie)
  483. assert.NoError(t, err)
  484. assert.Empty(t, oidcToken.SessionID)
  485. assert.False(t, oidcToken.isAdmin())
  486. assert.False(t, oidcToken.isExpired())
  487. if assert.Len(t, oidcToken.Permissions, 1) {
  488. assert.Equal(t, sdk.WebClientSharesDisabled, oidcToken.Permissions[0])
  489. }
  490. rr = httptest.NewRecorder()
  491. r, err = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
  492. assert.NoError(t, err)
  493. r.RequestURI = webClientProfilePath
  494. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  495. server.router.ServeHTTP(rr, r)
  496. assert.Equal(t, http.StatusOK, rr.Code)
  497. // the user can access the allowed pages
  498. rr = httptest.NewRecorder()
  499. r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
  500. assert.NoError(t, err)
  501. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  502. server.router.ServeHTTP(rr, r)
  503. assert.Equal(t, http.StatusOK, rr.Code)
  504. // try with an invalid cookie
  505. rr = httptest.NewRecorder()
  506. r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
  507. assert.NoError(t, err)
  508. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, xid.New().String()))
  509. server.router.ServeHTTP(rr, r)
  510. assert.Equal(t, http.StatusFound, rr.Code)
  511. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  512. // Web Admin is not available with a client cookie
  513. rr = httptest.NewRecorder()
  514. r, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
  515. assert.NoError(t, err)
  516. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  517. server.router.ServeHTTP(rr, r)
  518. assert.Equal(t, http.StatusFound, rr.Code)
  519. assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
  520. // logout the user
  521. rr = httptest.NewRecorder()
  522. r, err = http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
  523. assert.NoError(t, err)
  524. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  525. server.router.ServeHTTP(rr, r)
  526. assert.Equal(t, http.StatusFound, rr.Code)
  527. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  528. require.Len(t, oidcMgr.pendingAuths, 0)
  529. require.Len(t, oidcMgr.tokens, 0)
  530. err = os.RemoveAll(user.GetHomeDir())
  531. assert.NoError(t, err)
  532. err = dataprovider.DeleteUser(username, "", "", "")
  533. assert.NoError(t, err)
  534. }
  535. func TestOIDCRefreshToken(t *testing.T) {
  536. oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
  537. require.True(t, ok)
  538. r, err := http.NewRequest(http.MethodGet, webUsersPath, nil)
  539. assert.NoError(t, err)
  540. token := oidcToken{
  541. Cookie: xid.New().String(),
  542. AccessToken: xid.New().String(),
  543. TokenType: "Bearer",
  544. ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-1 * time.Minute)),
  545. Nonce: xid.New().String(),
  546. Role: adminRoleFieldValue,
  547. Username: defaultAdminUsername,
  548. }
  549. config := mockOAuth2Config{
  550. tokenSource: &mockTokenSource{
  551. err: common.ErrGenericFailure,
  552. },
  553. }
  554. verifier := mockOIDCVerifier{
  555. err: common.ErrGenericFailure,
  556. }
  557. err = token.refresh(context.Background(), &config, &verifier, r)
  558. if assert.Error(t, err) {
  559. assert.Contains(t, err.Error(), "refresh token not set")
  560. }
  561. token.RefreshToken = xid.New().String()
  562. err = token.refresh(context.Background(), &config, &verifier, r)
  563. assert.ErrorIs(t, err, common.ErrGenericFailure)
  564. newToken := &oauth2.Token{
  565. AccessToken: xid.New().String(),
  566. RefreshToken: xid.New().String(),
  567. Expiry: time.Now().Add(5 * time.Minute),
  568. }
  569. config = mockOAuth2Config{
  570. tokenSource: &mockTokenSource{
  571. token: newToken,
  572. },
  573. }
  574. verifier = mockOIDCVerifier{
  575. token: &oidc.IDToken{},
  576. }
  577. err = token.refresh(context.Background(), &config, &verifier, r)
  578. if assert.Error(t, err) {
  579. assert.Contains(t, err.Error(), "the refreshed token has no id token")
  580. }
  581. newToken = newToken.WithExtra(map[string]any{
  582. "id_token": "id_token_val",
  583. })
  584. newToken.Expiry = time.Time{}
  585. config = mockOAuth2Config{
  586. tokenSource: &mockTokenSource{
  587. token: newToken,
  588. },
  589. }
  590. verifier = mockOIDCVerifier{
  591. err: common.ErrGenericFailure,
  592. }
  593. err = token.refresh(context.Background(), &config, &verifier, r)
  594. assert.ErrorIs(t, err, common.ErrGenericFailure)
  595. newToken = newToken.WithExtra(map[string]any{
  596. "id_token": "id_token_val",
  597. })
  598. newToken.Expiry = time.Now().Add(5 * time.Minute)
  599. config = mockOAuth2Config{
  600. tokenSource: &mockTokenSource{
  601. token: newToken,
  602. },
  603. }
  604. verifier = mockOIDCVerifier{
  605. token: &oidc.IDToken{
  606. Nonce: xid.New().String(), // nonce is different from the expected one
  607. },
  608. }
  609. err = token.refresh(context.Background(), &config, &verifier, r)
  610. if assert.Error(t, err) {
  611. assert.Contains(t, err.Error(), "the refreshed token nonce mismatch")
  612. }
  613. verifier = mockOIDCVerifier{
  614. token: &oidc.IDToken{
  615. Nonce: "", // empty token is fine on refresh but claims are not set
  616. },
  617. }
  618. err = token.refresh(context.Background(), &config, &verifier, r)
  619. if assert.Error(t, err) {
  620. assert.Contains(t, err.Error(), "oidc: claims not set")
  621. }
  622. idToken := &oidc.IDToken{
  623. Nonce: token.Nonce,
  624. }
  625. setIDTokenClaims(idToken, []byte(`{"sid":"id_token_sid"}`))
  626. verifier = mockOIDCVerifier{
  627. token: idToken,
  628. }
  629. err = token.refresh(context.Background(), &config, &verifier, r)
  630. assert.NoError(t, err)
  631. assert.Len(t, token.Permissions, 1)
  632. token.Role = nil
  633. // user does not exist
  634. err = token.refresh(context.Background(), &config, &verifier, r)
  635. assert.Error(t, err)
  636. require.Len(t, oidcMgr.tokens, 1)
  637. oidcMgr.removeToken(token.Cookie)
  638. require.Len(t, oidcMgr.tokens, 0)
  639. }
  640. func TestOIDCRefreshUser(t *testing.T) {
  641. token := oidcToken{
  642. Cookie: xid.New().String(),
  643. AccessToken: xid.New().String(),
  644. TokenType: "Bearer",
  645. ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Minute)),
  646. Nonce: xid.New().String(),
  647. Role: adminRoleFieldValue,
  648. Username: "missing username",
  649. }
  650. r, err := http.NewRequest(http.MethodGet, webUsersPath, nil)
  651. assert.NoError(t, err)
  652. err = token.refreshUser(r)
  653. assert.Error(t, err)
  654. admin := dataprovider.Admin{
  655. Username: "test_oidc_admin_refresh",
  656. Password: "p",
  657. Permissions: []string{dataprovider.PermAdminAny},
  658. Status: 0,
  659. Filters: dataprovider.AdminFilters{
  660. Preferences: dataprovider.AdminPreferences{
  661. HideUserPageSections: 1 + 2 + 4,
  662. },
  663. },
  664. }
  665. err = dataprovider.AddAdmin(&admin, "", "", "")
  666. assert.NoError(t, err)
  667. token.Username = admin.Username
  668. err = token.refreshUser(r)
  669. if assert.Error(t, err) {
  670. assert.Contains(t, err.Error(), "is disabled")
  671. }
  672. admin.Status = 1
  673. err = dataprovider.UpdateAdmin(&admin, "", "", "")
  674. assert.NoError(t, err)
  675. err = token.refreshUser(r)
  676. assert.NoError(t, err)
  677. assert.Equal(t, admin.Permissions, token.Permissions)
  678. assert.Equal(t, admin.Filters.Preferences.HideUserPageSections, token.HideUserPageSections)
  679. err = dataprovider.DeleteAdmin(admin.Username, "", "", "")
  680. assert.NoError(t, err)
  681. username := "test_oidc_user_refresh_token"
  682. user := dataprovider.User{
  683. BaseUser: sdk.BaseUser{
  684. Username: username,
  685. Password: "p",
  686. HomeDir: filepath.Join(os.TempDir(), username),
  687. Status: 0,
  688. Permissions: map[string][]string{
  689. "/": {dataprovider.PermAny},
  690. },
  691. },
  692. Filters: dataprovider.UserFilters{
  693. BaseUserFilters: sdk.BaseUserFilters{
  694. DeniedProtocols: []string{common.ProtocolHTTP},
  695. WebClient: []string{sdk.WebClientSharesDisabled, sdk.WebClientWriteDisabled},
  696. },
  697. },
  698. }
  699. err = dataprovider.AddUser(&user, "", "", "")
  700. assert.NoError(t, err)
  701. r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
  702. assert.NoError(t, err)
  703. token.Role = nil
  704. token.Username = username
  705. assert.False(t, token.isAdmin())
  706. err = token.refreshUser(r)
  707. if assert.Error(t, err) {
  708. assert.Contains(t, err.Error(), "is disabled")
  709. }
  710. user, err = dataprovider.UserExists(username, "")
  711. assert.NoError(t, err)
  712. user.Status = 1
  713. err = dataprovider.UpdateUser(&user, "", "", "")
  714. assert.NoError(t, err)
  715. err = token.refreshUser(r)
  716. if assert.Error(t, err) {
  717. assert.Contains(t, err.Error(), "protocol HTTP is not allowed")
  718. }
  719. user.Filters.DeniedProtocols = []string{common.ProtocolFTP}
  720. err = dataprovider.UpdateUser(&user, "", "", "")
  721. assert.NoError(t, err)
  722. err = token.refreshUser(r)
  723. assert.NoError(t, err)
  724. assert.Equal(t, user.Filters.WebClient, token.Permissions)
  725. err = dataprovider.DeleteUser(username, "", "", "")
  726. assert.NoError(t, err)
  727. }
  728. func TestValidateOIDCToken(t *testing.T) {
  729. oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
  730. require.True(t, ok)
  731. server := getTestOIDCServer()
  732. err := server.binding.OIDC.initialize()
  733. assert.NoError(t, err)
  734. server.initializeRouter()
  735. rr := httptest.NewRecorder()
  736. r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
  737. assert.NoError(t, err)
  738. _, err = server.validateOIDCToken(rr, r, false)
  739. assert.ErrorIs(t, err, errInvalidToken)
  740. // expired token and refresh error
  741. server.binding.OIDC.oauth2Config = &mockOAuth2Config{
  742. tokenSource: &mockTokenSource{
  743. err: common.ErrGenericFailure,
  744. },
  745. }
  746. token := oidcToken{
  747. Cookie: xid.New().String(),
  748. AccessToken: xid.New().String(),
  749. ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-2 * time.Minute)),
  750. }
  751. oidcMgr.addToken(token)
  752. rr = httptest.NewRecorder()
  753. r, err = http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
  754. assert.NoError(t, err)
  755. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, token.Cookie))
  756. _, err = server.validateOIDCToken(rr, r, false)
  757. assert.ErrorIs(t, err, errInvalidToken)
  758. oidcMgr.removeToken(token.Cookie)
  759. assert.Len(t, oidcMgr.tokens, 0)
  760. server.tokenAuth = jwtauth.New("PS256", util.GenerateRandomBytes(32), nil)
  761. token = oidcToken{
  762. Cookie: xid.New().String(),
  763. AccessToken: xid.New().String(),
  764. }
  765. oidcMgr.addToken(token)
  766. rr = httptest.NewRecorder()
  767. r, err = http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
  768. assert.NoError(t, err)
  769. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, token.Cookie))
  770. server.router.ServeHTTP(rr, r)
  771. assert.Equal(t, http.StatusFound, rr.Code)
  772. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  773. oidcMgr.removeToken(token.Cookie)
  774. assert.Len(t, oidcMgr.tokens, 0)
  775. token = oidcToken{
  776. Cookie: xid.New().String(),
  777. AccessToken: xid.New().String(),
  778. Role: "admin",
  779. }
  780. oidcMgr.addToken(token)
  781. rr = httptest.NewRecorder()
  782. r, err = http.NewRequest(http.MethodGet, webLogoutPath, nil)
  783. assert.NoError(t, err)
  784. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, token.Cookie))
  785. server.router.ServeHTTP(rr, r)
  786. assert.Equal(t, http.StatusFound, rr.Code)
  787. assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
  788. oidcMgr.removeToken(token.Cookie)
  789. assert.Len(t, oidcMgr.tokens, 0)
  790. }
  791. func TestSkipOIDCAuth(t *testing.T) {
  792. server := getTestOIDCServer()
  793. err := server.binding.OIDC.initialize()
  794. assert.NoError(t, err)
  795. server.initializeRouter()
  796. jwtTokenClaims := jwtTokenClaims{
  797. Username: "user",
  798. }
  799. _, tokenString, err := jwtTokenClaims.createToken(server.tokenAuth, tokenAudienceWebClient, "")
  800. assert.NoError(t, err)
  801. rr := httptest.NewRecorder()
  802. r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
  803. assert.NoError(t, err)
  804. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", jwtCookieKey, tokenString))
  805. server.router.ServeHTTP(rr, r)
  806. assert.Equal(t, http.StatusFound, rr.Code)
  807. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  808. }
  809. func TestOIDCLogoutErrors(t *testing.T) {
  810. server := getTestOIDCServer()
  811. assert.Empty(t, server.binding.OIDC.providerLogoutURL)
  812. server.logoutFromOIDCOP("")
  813. server.binding.OIDC.providerLogoutURL = "http://foo\x7f.com/"
  814. server.doOIDCFromLogout("")
  815. server.binding.OIDC.providerLogoutURL = "http://127.0.0.1:11234"
  816. server.doOIDCFromLogout("")
  817. }
  818. func TestOIDCToken(t *testing.T) {
  819. admin := dataprovider.Admin{
  820. Username: "test_oidc_admin",
  821. Password: "p",
  822. Permissions: []string{dataprovider.PermAdminAny},
  823. Status: 0,
  824. }
  825. err := dataprovider.AddAdmin(&admin, "", "", "")
  826. assert.NoError(t, err)
  827. token := oidcToken{
  828. Username: admin.Username,
  829. }
  830. // role not initialized, user with the specified username does not exist
  831. req, err := http.NewRequest(http.MethodGet, webUsersPath, nil)
  832. assert.NoError(t, err)
  833. err = token.getUser(req)
  834. assert.ErrorIs(t, err, util.ErrNotFound)
  835. token.Role = "admin"
  836. req, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
  837. assert.NoError(t, err)
  838. err = token.getUser(req)
  839. if assert.Error(t, err) {
  840. assert.Contains(t, err.Error(), "is disabled")
  841. }
  842. err = dataprovider.DeleteAdmin(admin.Username, "", "", "")
  843. assert.NoError(t, err)
  844. username := "test_oidc_user"
  845. token.Username = username
  846. token.Role = ""
  847. err = token.getUser(req)
  848. if assert.Error(t, err) {
  849. assert.ErrorIs(t, err, util.ErrNotFound)
  850. }
  851. user := dataprovider.User{
  852. BaseUser: sdk.BaseUser{
  853. Username: username,
  854. Password: "p",
  855. HomeDir: filepath.Join(os.TempDir(), username),
  856. Status: 0,
  857. Permissions: map[string][]string{
  858. "/": {dataprovider.PermAny},
  859. },
  860. },
  861. Filters: dataprovider.UserFilters{
  862. BaseUserFilters: sdk.BaseUserFilters{
  863. DeniedProtocols: []string{common.ProtocolHTTP},
  864. },
  865. },
  866. }
  867. err = dataprovider.AddUser(&user, "", "", "")
  868. assert.NoError(t, err)
  869. err = token.getUser(req)
  870. if assert.Error(t, err) {
  871. assert.Contains(t, err.Error(), "is disabled")
  872. }
  873. user, err = dataprovider.UserExists(username, "")
  874. assert.NoError(t, err)
  875. user.Status = 1
  876. user.Password = "np"
  877. err = dataprovider.UpdateUser(&user, "", "", "")
  878. assert.NoError(t, err)
  879. err = token.getUser(req)
  880. if assert.Error(t, err) {
  881. assert.Contains(t, err.Error(), "protocol HTTP is not allowed")
  882. }
  883. user.Filters.DeniedProtocols = nil
  884. user.FsConfig.Provider = sdk.SFTPFilesystemProvider
  885. user.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
  886. BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
  887. Endpoint: "127.0.0.1:8022",
  888. Username: username,
  889. },
  890. Password: kms.NewPlainSecret("np"),
  891. }
  892. err = dataprovider.UpdateUser(&user, "", "", "")
  893. assert.NoError(t, err)
  894. err = token.getUser(req)
  895. if assert.Error(t, err) {
  896. assert.Contains(t, err.Error(), "SFTP loop")
  897. }
  898. common.Config.PostConnectHook = fmt.Sprintf("http://%v/404", oidcMockAddr)
  899. err = token.getUser(req)
  900. if assert.Error(t, err) {
  901. assert.Contains(t, err.Error(), "access denied")
  902. }
  903. common.Config.PostConnectHook = ""
  904. err = os.RemoveAll(user.GetHomeDir())
  905. assert.NoError(t, err)
  906. err = dataprovider.DeleteUser(username, "", "", "")
  907. assert.NoError(t, err)
  908. }
  909. func TestOIDCImplicitRoles(t *testing.T) {
  910. oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
  911. require.True(t, ok)
  912. server := getTestOIDCServer()
  913. server.binding.OIDC.ImplicitRoles = true
  914. err := server.binding.OIDC.initialize()
  915. assert.NoError(t, err)
  916. server.initializeRouter()
  917. authReq := newOIDCPendingAuth(tokenAudienceWebAdmin)
  918. oidcMgr.addPendingAuth(authReq)
  919. token := &oauth2.Token{
  920. AccessToken: "1234",
  921. Expiry: time.Now().Add(5 * time.Minute),
  922. }
  923. token = token.WithExtra(map[string]any{
  924. "id_token": "id_token_val",
  925. })
  926. server.binding.OIDC.oauth2Config = &mockOAuth2Config{
  927. tokenSource: &mockTokenSource{},
  928. authCodeURL: webOIDCRedirectPath,
  929. token: token,
  930. }
  931. idToken := &oidc.IDToken{
  932. Nonce: authReq.Nonce,
  933. Expiry: time.Now().Add(5 * time.Minute),
  934. }
  935. setIDTokenClaims(idToken, []byte(`{"preferred_username":"admin","sid":"sid456"}`))
  936. server.binding.OIDC.verifier = &mockOIDCVerifier{
  937. err: nil,
  938. token: idToken,
  939. }
  940. rr := httptest.NewRecorder()
  941. r, err := http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  942. assert.NoError(t, err)
  943. server.router.ServeHTTP(rr, r)
  944. assert.Equal(t, http.StatusFound, rr.Code)
  945. assert.Equal(t, webUsersPath, rr.Header().Get("Location"))
  946. require.Len(t, oidcMgr.pendingAuths, 0)
  947. require.Len(t, oidcMgr.tokens, 1)
  948. var tokenCookie string
  949. for k := range oidcMgr.tokens {
  950. tokenCookie = k
  951. }
  952. // Web Client is not available with an admin token
  953. rr = httptest.NewRecorder()
  954. r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
  955. assert.NoError(t, err)
  956. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  957. server.router.ServeHTTP(rr, r)
  958. assert.Equal(t, http.StatusFound, rr.Code)
  959. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  960. // logout the admin user
  961. rr = httptest.NewRecorder()
  962. r, err = http.NewRequest(http.MethodGet, webLogoutPath, nil)
  963. assert.NoError(t, err)
  964. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  965. server.router.ServeHTTP(rr, r)
  966. assert.Equal(t, http.StatusFound, rr.Code)
  967. assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
  968. require.Len(t, oidcMgr.pendingAuths, 0)
  969. require.Len(t, oidcMgr.tokens, 0)
  970. // now login and logout a user
  971. username := "test_oidc_implicit_user"
  972. user := dataprovider.User{
  973. BaseUser: sdk.BaseUser{
  974. Username: username,
  975. Password: "pwd",
  976. HomeDir: filepath.Join(os.TempDir(), username),
  977. Status: 1,
  978. Permissions: map[string][]string{
  979. "/": {dataprovider.PermAny},
  980. },
  981. },
  982. Filters: dataprovider.UserFilters{
  983. BaseUserFilters: sdk.BaseUserFilters{
  984. WebClient: []string{sdk.WebClientSharesDisabled},
  985. },
  986. },
  987. }
  988. err = dataprovider.AddUser(&user, "", "", "")
  989. assert.NoError(t, err)
  990. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  991. oidcMgr.addPendingAuth(authReq)
  992. idToken = &oidc.IDToken{
  993. Nonce: authReq.Nonce,
  994. Expiry: time.Now().Add(5 * time.Minute),
  995. }
  996. setIDTokenClaims(idToken, []byte(`{"preferred_username":"test_oidc_implicit_user"}`))
  997. server.binding.OIDC.verifier = &mockOIDCVerifier{
  998. err: nil,
  999. token: idToken,
  1000. }
  1001. rr = httptest.NewRecorder()
  1002. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  1003. assert.NoError(t, err)
  1004. server.router.ServeHTTP(rr, r)
  1005. assert.Equal(t, http.StatusFound, rr.Code)
  1006. assert.Equal(t, webClientFilesPath, rr.Header().Get("Location"))
  1007. require.Len(t, oidcMgr.pendingAuths, 0)
  1008. require.Len(t, oidcMgr.tokens, 1)
  1009. for k := range oidcMgr.tokens {
  1010. tokenCookie = k
  1011. }
  1012. rr = httptest.NewRecorder()
  1013. r, err = http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
  1014. assert.NoError(t, err)
  1015. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  1016. server.router.ServeHTTP(rr, r)
  1017. assert.Equal(t, http.StatusFound, rr.Code)
  1018. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  1019. require.Len(t, oidcMgr.pendingAuths, 0)
  1020. require.Len(t, oidcMgr.tokens, 0)
  1021. err = os.RemoveAll(user.GetHomeDir())
  1022. assert.NoError(t, err)
  1023. err = dataprovider.DeleteUser(username, "", "", "")
  1024. assert.NoError(t, err)
  1025. }
  1026. func TestMemoryOIDCManager(t *testing.T) {
  1027. oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
  1028. require.True(t, ok)
  1029. require.Len(t, oidcMgr.pendingAuths, 0)
  1030. authReq := newOIDCPendingAuth(tokenAudienceWebAdmin)
  1031. oidcMgr.addPendingAuth(authReq)
  1032. require.Len(t, oidcMgr.pendingAuths, 1)
  1033. _, err := oidcMgr.getPendingAuth(authReq.State)
  1034. assert.NoError(t, err)
  1035. oidcMgr.removePendingAuth(authReq.State)
  1036. require.Len(t, oidcMgr.pendingAuths, 0)
  1037. authReq.IssuedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-61 * time.Second))
  1038. oidcMgr.addPendingAuth(authReq)
  1039. require.Len(t, oidcMgr.pendingAuths, 1)
  1040. _, err = oidcMgr.getPendingAuth(authReq.State)
  1041. if assert.Error(t, err) {
  1042. assert.Contains(t, err.Error(), "too old")
  1043. }
  1044. oidcMgr.cleanup()
  1045. require.Len(t, oidcMgr.pendingAuths, 0)
  1046. token := oidcToken{
  1047. AccessToken: xid.New().String(),
  1048. Nonce: xid.New().String(),
  1049. SessionID: xid.New().String(),
  1050. Cookie: xid.New().String(),
  1051. Username: xid.New().String(),
  1052. Role: "admin",
  1053. Permissions: []string{dataprovider.PermAdminAny},
  1054. }
  1055. require.Len(t, oidcMgr.tokens, 0)
  1056. oidcMgr.addToken(token)
  1057. require.Len(t, oidcMgr.tokens, 1)
  1058. _, err = oidcMgr.getToken(xid.New().String())
  1059. assert.Error(t, err)
  1060. storedToken, err := oidcMgr.getToken(token.Cookie)
  1061. assert.NoError(t, err)
  1062. token.UsedAt = 0 // ensure we don't modify the stored token
  1063. assert.Greater(t, storedToken.UsedAt, int64(0))
  1064. token.UsedAt = storedToken.UsedAt
  1065. assert.Equal(t, token, storedToken)
  1066. // the usage will not be updated, it is recent
  1067. oidcMgr.updateTokenUsage(storedToken)
  1068. storedToken, err = oidcMgr.getToken(token.Cookie)
  1069. assert.NoError(t, err)
  1070. assert.Equal(t, token, storedToken)
  1071. usedAt := util.GetTimeAsMsSinceEpoch(time.Now().Add(-5 * time.Minute))
  1072. storedToken.UsedAt = usedAt
  1073. oidcMgr.tokens[token.Cookie] = storedToken
  1074. storedToken, err = oidcMgr.getToken(token.Cookie)
  1075. assert.NoError(t, err)
  1076. assert.Equal(t, usedAt, storedToken.UsedAt)
  1077. token.UsedAt = storedToken.UsedAt
  1078. assert.Equal(t, token, storedToken)
  1079. oidcMgr.updateTokenUsage(storedToken)
  1080. storedToken, err = oidcMgr.getToken(token.Cookie)
  1081. assert.NoError(t, err)
  1082. assert.Greater(t, storedToken.UsedAt, usedAt)
  1083. token.UsedAt = storedToken.UsedAt
  1084. assert.Equal(t, token, storedToken)
  1085. storedToken.UsedAt = util.GetTimeAsMsSinceEpoch(time.Now()) - tokenDeleteInterval - 1
  1086. oidcMgr.tokens[token.Cookie] = storedToken
  1087. storedToken, err = oidcMgr.getToken(token.Cookie)
  1088. if assert.Error(t, err) {
  1089. assert.Contains(t, err.Error(), "token is too old")
  1090. }
  1091. oidcMgr.removeToken(xid.New().String())
  1092. require.Len(t, oidcMgr.tokens, 1)
  1093. oidcMgr.removeToken(token.Cookie)
  1094. require.Len(t, oidcMgr.tokens, 0)
  1095. oidcMgr.addToken(token)
  1096. usedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-6 * time.Hour))
  1097. token.UsedAt = usedAt
  1098. oidcMgr.tokens[token.Cookie] = token
  1099. newToken := oidcToken{
  1100. Cookie: xid.New().String(),
  1101. }
  1102. oidcMgr.addToken(newToken)
  1103. oidcMgr.cleanup()
  1104. require.Len(t, oidcMgr.tokens, 1)
  1105. _, err = oidcMgr.getToken(token.Cookie)
  1106. assert.Error(t, err)
  1107. _, err = oidcMgr.getToken(newToken.Cookie)
  1108. assert.NoError(t, err)
  1109. oidcMgr.removeToken(newToken.Cookie)
  1110. require.Len(t, oidcMgr.tokens, 0)
  1111. }
  1112. func TestOIDCEvMgrIntegration(t *testing.T) {
  1113. providerConf := dataprovider.GetProviderConfig()
  1114. err := dataprovider.Close()
  1115. assert.NoError(t, err)
  1116. newProviderConf := providerConf
  1117. newProviderConf.NamingRules = 5
  1118. err = dataprovider.Initialize(newProviderConf, configDir, true)
  1119. assert.NoError(t, err)
  1120. // add a special chars to check json replacer
  1121. username := `test_"oidc_eventmanager`
  1122. u := map[string]any{
  1123. "username": "{{Name}}",
  1124. "status": 1,
  1125. "home_dir": filepath.Join(os.TempDir(), "{{IDPFieldcustom1.sub}}"),
  1126. "permissions": map[string][]string{
  1127. "/": {dataprovider.PermAny},
  1128. },
  1129. "description": "{{IDPFieldcustom2}}",
  1130. }
  1131. userTmpl, err := json.Marshal(u)
  1132. require.NoError(t, err)
  1133. a := map[string]any{
  1134. "username": "{{Name}}",
  1135. "status": 1,
  1136. "permissions": []string{dataprovider.PermAdminAny},
  1137. }
  1138. adminTmpl, err := json.Marshal(a)
  1139. require.NoError(t, err)
  1140. action := &dataprovider.BaseEventAction{
  1141. Name: "a",
  1142. Type: dataprovider.ActionTypeIDPAccountCheck,
  1143. Options: dataprovider.BaseEventActionOptions{
  1144. IDPConfig: dataprovider.EventActionIDPAccountCheck{
  1145. Mode: 0,
  1146. TemplateUser: string(userTmpl),
  1147. TemplateAdmin: string(adminTmpl),
  1148. },
  1149. },
  1150. }
  1151. err = dataprovider.AddEventAction(action, "", "", "")
  1152. assert.NoError(t, err)
  1153. rule := &dataprovider.EventRule{
  1154. Name: "r",
  1155. Status: 1,
  1156. Trigger: dataprovider.EventTriggerIDPLogin,
  1157. Conditions: dataprovider.EventConditions{
  1158. IDPLoginEvent: 0,
  1159. },
  1160. Actions: []dataprovider.EventAction{
  1161. {
  1162. BaseEventAction: dataprovider.BaseEventAction{
  1163. Name: action.Name,
  1164. },
  1165. Options: dataprovider.EventActionOptions{
  1166. ExecuteSync: true,
  1167. },
  1168. },
  1169. },
  1170. }
  1171. err = dataprovider.AddEventRule(rule, "", "", "")
  1172. assert.NoError(t, err)
  1173. oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
  1174. require.True(t, ok)
  1175. server := getTestOIDCServer()
  1176. server.binding.OIDC.ImplicitRoles = true
  1177. server.binding.OIDC.CustomFields = []string{"custom1.sub", "custom2"}
  1178. err = server.binding.OIDC.initialize()
  1179. assert.NoError(t, err)
  1180. server.initializeRouter()
  1181. // login a user with OIDC
  1182. _, err = dataprovider.UserExists(username, "")
  1183. assert.ErrorIs(t, err, util.ErrNotFound)
  1184. authReq := newOIDCPendingAuth(tokenAudienceWebClient)
  1185. oidcMgr.addPendingAuth(authReq)
  1186. token := &oauth2.Token{
  1187. AccessToken: "1234",
  1188. Expiry: time.Now().Add(5 * time.Minute),
  1189. }
  1190. token = token.WithExtra(map[string]any{
  1191. "id_token": "id_token_val",
  1192. })
  1193. server.binding.OIDC.oauth2Config = &mockOAuth2Config{
  1194. tokenSource: &mockTokenSource{},
  1195. authCodeURL: webOIDCRedirectPath,
  1196. token: token,
  1197. }
  1198. idToken := &oidc.IDToken{
  1199. Nonce: authReq.Nonce,
  1200. Expiry: time.Now().Add(5 * time.Minute),
  1201. }
  1202. setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+util.JSONEscape(username)+`","custom1":{"sub":"val1"},"custom2":"desc"}`)) //nolint:goconst
  1203. server.binding.OIDC.verifier = &mockOIDCVerifier{
  1204. err: nil,
  1205. token: idToken,
  1206. }
  1207. rr := httptest.NewRecorder()
  1208. r, err := http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  1209. assert.NoError(t, err)
  1210. server.router.ServeHTTP(rr, r)
  1211. assert.Equal(t, http.StatusFound, rr.Code)
  1212. assert.Equal(t, webClientFilesPath, rr.Header().Get("Location"))
  1213. user, err := dataprovider.UserExists(username, "")
  1214. assert.NoError(t, err)
  1215. assert.Equal(t, filepath.Join(os.TempDir(), "val1"), user.GetHomeDir())
  1216. assert.Equal(t, "desc", user.Description)
  1217. err = dataprovider.DeleteUser(username, "", "", "")
  1218. assert.NoError(t, err)
  1219. err = os.RemoveAll(user.GetHomeDir())
  1220. assert.NoError(t, err)
  1221. // login an admin with OIDC
  1222. _, err = dataprovider.AdminExists(username)
  1223. assert.ErrorIs(t, err, util.ErrNotFound)
  1224. authReq = newOIDCPendingAuth(tokenAudienceWebAdmin)
  1225. oidcMgr.addPendingAuth(authReq)
  1226. idToken = &oidc.IDToken{
  1227. Nonce: authReq.Nonce,
  1228. Expiry: time.Now().Add(5 * time.Minute),
  1229. }
  1230. setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+util.JSONEscape(username)+`"}`))
  1231. server.binding.OIDC.verifier = &mockOIDCVerifier{
  1232. err: nil,
  1233. token: idToken,
  1234. }
  1235. rr = httptest.NewRecorder()
  1236. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  1237. assert.NoError(t, err)
  1238. server.router.ServeHTTP(rr, r)
  1239. assert.Equal(t, http.StatusFound, rr.Code)
  1240. assert.Equal(t, webUsersPath, rr.Header().Get("Location"))
  1241. _, err = dataprovider.AdminExists(username)
  1242. assert.NoError(t, err)
  1243. err = dataprovider.DeleteAdmin(username, "", "", "")
  1244. assert.NoError(t, err)
  1245. // set invalid templates and try again
  1246. action.Options.IDPConfig.TemplateUser = `{}`
  1247. action.Options.IDPConfig.TemplateAdmin = `{}`
  1248. err = dataprovider.UpdateEventAction(action, "", "", "")
  1249. assert.NoError(t, err)
  1250. for _, audience := range []string{tokenAudienceWebAdmin, tokenAudienceWebClient} {
  1251. authReq = newOIDCPendingAuth(audience)
  1252. oidcMgr.addPendingAuth(authReq)
  1253. idToken = &oidc.IDToken{
  1254. Nonce: authReq.Nonce,
  1255. Expiry: time.Now().Add(5 * time.Minute),
  1256. }
  1257. setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+util.JSONEscape(username)+`"}`))
  1258. server.binding.OIDC.verifier = &mockOIDCVerifier{
  1259. err: nil,
  1260. token: idToken,
  1261. }
  1262. rr = httptest.NewRecorder()
  1263. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  1264. assert.NoError(t, err)
  1265. server.router.ServeHTTP(rr, r)
  1266. assert.Equal(t, http.StatusFound, rr.Code)
  1267. }
  1268. for k := range oidcMgr.tokens {
  1269. oidcMgr.removeToken(k)
  1270. }
  1271. err = dataprovider.DeleteEventRule(rule.Name, "", "", "")
  1272. assert.NoError(t, err)
  1273. err = dataprovider.DeleteEventAction(action.Name, "", "", "")
  1274. assert.NoError(t, err)
  1275. err = dataprovider.Close()
  1276. assert.NoError(t, err)
  1277. err = dataprovider.Initialize(providerConf, configDir, true)
  1278. assert.NoError(t, err)
  1279. }
  1280. func TestOIDCPreLoginHook(t *testing.T) {
  1281. if runtime.GOOS == osWindows {
  1282. t.Skip("this test is not available on Windows")
  1283. }
  1284. oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
  1285. require.True(t, ok)
  1286. username := "test_oidc_user_prelogin"
  1287. u := dataprovider.User{
  1288. BaseUser: sdk.BaseUser{
  1289. Username: username,
  1290. HomeDir: filepath.Join(os.TempDir(), username),
  1291. Status: 1,
  1292. Permissions: map[string][]string{
  1293. "/": {dataprovider.PermAny},
  1294. },
  1295. },
  1296. }
  1297. preLoginPath := filepath.Join(os.TempDir(), "prelogin.sh")
  1298. providerConf := dataprovider.GetProviderConfig()
  1299. err := dataprovider.Close()
  1300. assert.NoError(t, err)
  1301. err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
  1302. assert.NoError(t, err)
  1303. newProviderConf := providerConf
  1304. newProviderConf.PreLoginHook = preLoginPath
  1305. err = dataprovider.Initialize(newProviderConf, configDir, true)
  1306. assert.NoError(t, err)
  1307. server := getTestOIDCServer()
  1308. server.binding.OIDC.CustomFields = []string{"field1", "field2"}
  1309. err = server.binding.OIDC.initialize()
  1310. assert.NoError(t, err)
  1311. server.initializeRouter()
  1312. _, err = dataprovider.UserExists(username, "")
  1313. assert.ErrorIs(t, err, util.ErrNotFound)
  1314. // now login with OIDC
  1315. authReq := newOIDCPendingAuth(tokenAudienceWebClient)
  1316. oidcMgr.addPendingAuth(authReq)
  1317. token := &oauth2.Token{
  1318. AccessToken: "1234",
  1319. Expiry: time.Now().Add(5 * time.Minute),
  1320. }
  1321. token = token.WithExtra(map[string]any{
  1322. "id_token": "id_token_val",
  1323. })
  1324. server.binding.OIDC.oauth2Config = &mockOAuth2Config{
  1325. tokenSource: &mockTokenSource{},
  1326. authCodeURL: webOIDCRedirectPath,
  1327. token: token,
  1328. }
  1329. idToken := &oidc.IDToken{
  1330. Nonce: authReq.Nonce,
  1331. Expiry: time.Now().Add(5 * time.Minute),
  1332. }
  1333. setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+username+`"}`))
  1334. server.binding.OIDC.verifier = &mockOIDCVerifier{
  1335. err: nil,
  1336. token: idToken,
  1337. }
  1338. rr := httptest.NewRecorder()
  1339. r, err := http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  1340. assert.NoError(t, err)
  1341. server.router.ServeHTTP(rr, r)
  1342. assert.Equal(t, http.StatusFound, rr.Code)
  1343. assert.Equal(t, webClientFilesPath, rr.Header().Get("Location"))
  1344. _, err = dataprovider.UserExists(username, "")
  1345. assert.NoError(t, err)
  1346. err = dataprovider.DeleteUser(username, "", "", "")
  1347. assert.NoError(t, err)
  1348. err = os.RemoveAll(u.HomeDir)
  1349. assert.NoError(t, err)
  1350. err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, true), os.ModePerm)
  1351. assert.NoError(t, err)
  1352. authReq = newOIDCPendingAuth(tokenAudienceWebClient)
  1353. oidcMgr.addPendingAuth(authReq)
  1354. idToken = &oidc.IDToken{
  1355. Nonce: authReq.Nonce,
  1356. Expiry: time.Now().Add(5 * time.Minute),
  1357. }
  1358. setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+username+`","field1":"value1","field2":"value2","field3":"value3"}`))
  1359. server.binding.OIDC.verifier = &mockOIDCVerifier{
  1360. err: nil,
  1361. token: idToken,
  1362. }
  1363. rr = httptest.NewRecorder()
  1364. r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  1365. assert.NoError(t, err)
  1366. server.router.ServeHTTP(rr, r)
  1367. assert.Equal(t, http.StatusFound, rr.Code)
  1368. assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
  1369. _, err = dataprovider.UserExists(username, "")
  1370. assert.ErrorIs(t, err, util.ErrNotFound)
  1371. if assert.Len(t, oidcMgr.tokens, 1) {
  1372. for k := range oidcMgr.tokens {
  1373. oidcMgr.removeToken(k)
  1374. }
  1375. }
  1376. require.Len(t, oidcMgr.pendingAuths, 0)
  1377. require.Len(t, oidcMgr.tokens, 0)
  1378. err = dataprovider.Close()
  1379. assert.NoError(t, err)
  1380. err = dataprovider.Initialize(providerConf, configDir, true)
  1381. assert.NoError(t, err)
  1382. err = os.Remove(preLoginPath)
  1383. assert.NoError(t, err)
  1384. }
  1385. func TestOIDCIsAdmin(t *testing.T) {
  1386. type test struct {
  1387. input any
  1388. want bool
  1389. }
  1390. emptySlice := make([]any, 0)
  1391. tests := []test{
  1392. {input: "admin", want: true},
  1393. {input: append(emptySlice, "admin"), want: true},
  1394. {input: append(emptySlice, "user", "admin"), want: true},
  1395. {input: "user", want: false},
  1396. {input: emptySlice, want: false},
  1397. {input: append(emptySlice, 1), want: false},
  1398. {input: 1, want: false},
  1399. {input: nil, want: false},
  1400. {input: map[string]string{"admin": "admin"}, want: false},
  1401. }
  1402. for _, tc := range tests {
  1403. token := oidcToken{
  1404. Role: tc.input,
  1405. }
  1406. assert.Equal(t, tc.want, token.isAdmin(), "%v should return %t", tc.input, tc.want)
  1407. }
  1408. }
  1409. func TestParseAdminRole(t *testing.T) {
  1410. claims := make(map[string]any)
  1411. rawClaims := []byte(`{
  1412. "sub": "35666371",
  1413. "email": "[email protected]",
  1414. "preferred_username": "Sally",
  1415. "name": "Sally Tyler",
  1416. "updated_at": "2018-04-13T22:08:45Z",
  1417. "given_name": "Sally",
  1418. "family_name": "Tyler",
  1419. "params": {
  1420. "sftpgo_role": "admin",
  1421. "subparams": {
  1422. "sftpgo_role": "admin",
  1423. "inner": {
  1424. "sftpgo_role": ["user","admin"]
  1425. }
  1426. }
  1427. },
  1428. "at_hash": "lPLhxI2wjEndc-WfyroDZA",
  1429. "rt_hash": "mCmxPtA04N-55AxlEUbq-A",
  1430. "aud": "78d1d040-20c9-0136-5146-067351775fae92920",
  1431. "exp": 1523664997,
  1432. "iat": 1523657797
  1433. }`)
  1434. err := json.Unmarshal(rawClaims, &claims)
  1435. assert.NoError(t, err)
  1436. type test struct {
  1437. input string
  1438. want bool
  1439. val any
  1440. }
  1441. tests := []test{
  1442. {input: "", want: false},
  1443. {input: "sftpgo_role", want: false},
  1444. {input: "params.sftpgo_role", want: true, val: "admin"},
  1445. {input: "params.subparams.sftpgo_role", want: true, val: "admin"},
  1446. {input: "params.subparams.inner.sftpgo_role", want: true, val: []any{"user", "admin"}},
  1447. {input: "email", want: false},
  1448. {input: "missing", want: false},
  1449. {input: "params.email", want: false},
  1450. {input: "missing.sftpgo_role", want: false},
  1451. {input: "params", want: false},
  1452. {input: "params.subparams.inner.sftpgo_role.missing", want: false},
  1453. }
  1454. for _, tc := range tests {
  1455. token := oidcToken{}
  1456. token.getRoleFromField(claims, tc.input)
  1457. assert.Equal(t, tc.want, token.isAdmin(), "%q should return %t", tc.input, tc.want)
  1458. if tc.want {
  1459. assert.Equal(t, tc.val, token.Role)
  1460. }
  1461. }
  1462. }
  1463. func TestOIDCWithLoginFormsDisabled(t *testing.T) {
  1464. oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
  1465. require.True(t, ok)
  1466. server := getTestOIDCServer()
  1467. server.binding.OIDC.ImplicitRoles = true
  1468. server.binding.EnabledLoginMethods = 3
  1469. server.binding.EnableWebAdmin = true
  1470. server.binding.EnableWebClient = true
  1471. err := server.binding.OIDC.initialize()
  1472. assert.NoError(t, err)
  1473. server.initializeRouter()
  1474. // login with an admin user
  1475. authReq := newOIDCPendingAuth(tokenAudienceWebAdmin)
  1476. oidcMgr.addPendingAuth(authReq)
  1477. token := &oauth2.Token{
  1478. AccessToken: "1234",
  1479. Expiry: time.Now().Add(5 * time.Minute),
  1480. }
  1481. token = token.WithExtra(map[string]any{
  1482. "id_token": "id_token_val",
  1483. })
  1484. server.binding.OIDC.oauth2Config = &mockOAuth2Config{
  1485. tokenSource: &mockTokenSource{},
  1486. authCodeURL: webOIDCRedirectPath,
  1487. token: token,
  1488. }
  1489. idToken := &oidc.IDToken{
  1490. Nonce: authReq.Nonce,
  1491. Expiry: time.Now().Add(5 * time.Minute),
  1492. }
  1493. setIDTokenClaims(idToken, []byte(`{"preferred_username":"admin","sid":"sid456"}`))
  1494. server.binding.OIDC.verifier = &mockOIDCVerifier{
  1495. err: nil,
  1496. token: idToken,
  1497. }
  1498. rr := httptest.NewRecorder()
  1499. r, err := http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
  1500. assert.NoError(t, err)
  1501. server.router.ServeHTTP(rr, r)
  1502. assert.Equal(t, http.StatusFound, rr.Code)
  1503. assert.Equal(t, webUsersPath, rr.Header().Get("Location"))
  1504. var tokenCookie string
  1505. for k := range oidcMgr.tokens {
  1506. tokenCookie = k
  1507. }
  1508. // we should be able to create admins without setting a password
  1509. if csrfTokenAuth == nil {
  1510. csrfTokenAuth = jwtauth.New(jwa.HS256.String(), util.GenerateRandomBytes(32), nil)
  1511. }
  1512. adminUsername := "testAdmin"
  1513. form := make(url.Values)
  1514. form.Set(csrfFormToken, createCSRFToken(""))
  1515. form.Set("username", adminUsername)
  1516. form.Set("password", "")
  1517. form.Set("status", "1")
  1518. form.Set("permissions", "*")
  1519. rr = httptest.NewRecorder()
  1520. r, err = http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
  1521. assert.NoError(t, err)
  1522. r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
  1523. r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  1524. server.router.ServeHTTP(rr, r)
  1525. assert.Equal(t, http.StatusSeeOther, rr.Code)
  1526. _, err = dataprovider.AdminExists(adminUsername)
  1527. assert.NoError(t, err)
  1528. err = dataprovider.DeleteAdmin(adminUsername, "", "", "")
  1529. assert.NoError(t, err)
  1530. // login and password related routes are disabled
  1531. rr = httptest.NewRecorder()
  1532. r, err = http.NewRequest(http.MethodPost, webAdminLoginPath, nil)
  1533. assert.NoError(t, err)
  1534. server.router.ServeHTTP(rr, r)
  1535. assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
  1536. rr = httptest.NewRecorder()
  1537. r, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, nil)
  1538. assert.NoError(t, err)
  1539. server.router.ServeHTTP(rr, r)
  1540. assert.Equal(t, http.StatusNotFound, rr.Code)
  1541. rr = httptest.NewRecorder()
  1542. r, err = http.NewRequest(http.MethodPost, webClientLoginPath, nil)
  1543. assert.NoError(t, err)
  1544. server.router.ServeHTTP(rr, r)
  1545. assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
  1546. rr = httptest.NewRecorder()
  1547. r, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, nil)
  1548. assert.NoError(t, err)
  1549. server.router.ServeHTTP(rr, r)
  1550. assert.Equal(t, http.StatusNotFound, rr.Code)
  1551. }
  1552. func TestDbOIDCManager(t *testing.T) {
  1553. if !isSharedProviderSupported() {
  1554. t.Skip("this test it is not available with this provider")
  1555. }
  1556. mgr := newOIDCManager(1)
  1557. pendingAuth := newOIDCPendingAuth(tokenAudienceWebAdmin)
  1558. mgr.addPendingAuth(pendingAuth)
  1559. authReq, err := mgr.getPendingAuth(pendingAuth.State)
  1560. assert.NoError(t, err)
  1561. assert.Equal(t, pendingAuth, authReq)
  1562. pendingAuth.IssuedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-24 * time.Hour))
  1563. mgr.addPendingAuth(pendingAuth)
  1564. _, err = mgr.getPendingAuth(pendingAuth.State)
  1565. if assert.Error(t, err) {
  1566. assert.Contains(t, err.Error(), "auth request is too old")
  1567. }
  1568. mgr.removePendingAuth(pendingAuth.State)
  1569. _, err = mgr.getPendingAuth(pendingAuth.State)
  1570. if assert.Error(t, err) {
  1571. assert.Contains(t, err.Error(), "unable to get the auth request for the specified state")
  1572. }
  1573. mgr.addPendingAuth(pendingAuth)
  1574. _, err = mgr.getPendingAuth(pendingAuth.State)
  1575. if assert.Error(t, err) {
  1576. assert.Contains(t, err.Error(), "auth request is too old")
  1577. }
  1578. mgr.cleanup()
  1579. _, err = mgr.getPendingAuth(pendingAuth.State)
  1580. if assert.Error(t, err) {
  1581. assert.Contains(t, err.Error(), "unable to get the auth request for the specified state")
  1582. }
  1583. token := oidcToken{
  1584. Cookie: xid.New().String(),
  1585. AccessToken: xid.New().String(),
  1586. TokenType: "Bearer",
  1587. RefreshToken: xid.New().String(),
  1588. ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-2 * time.Minute)),
  1589. SessionID: xid.New().String(),
  1590. IDToken: xid.New().String(),
  1591. Nonce: xid.New().String(),
  1592. Username: xid.New().String(),
  1593. Permissions: []string{dataprovider.PermAdminAny},
  1594. Role: "admin",
  1595. }
  1596. mgr.addToken(token)
  1597. tokenGet, err := mgr.getToken(token.Cookie)
  1598. assert.NoError(t, err)
  1599. assert.Greater(t, tokenGet.UsedAt, int64(0))
  1600. token.UsedAt = tokenGet.UsedAt
  1601. assert.Equal(t, token, tokenGet)
  1602. time.Sleep(100 * time.Millisecond)
  1603. mgr.updateTokenUsage(token)
  1604. // no change
  1605. tokenGet, err = mgr.getToken(token.Cookie)
  1606. assert.NoError(t, err)
  1607. assert.Equal(t, token.UsedAt, tokenGet.UsedAt)
  1608. tokenGet.UsedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-24 * time.Hour))
  1609. tokenGet.RefreshToken = xid.New().String()
  1610. mgr.updateTokenUsage(tokenGet)
  1611. tokenGet, err = mgr.getToken(token.Cookie)
  1612. assert.NoError(t, err)
  1613. assert.NotEmpty(t, tokenGet.RefreshToken)
  1614. assert.NotEqual(t, token.RefreshToken, tokenGet.RefreshToken)
  1615. assert.Greater(t, tokenGet.UsedAt, token.UsedAt)
  1616. mgr.removeToken(token.Cookie)
  1617. tokenGet, err = mgr.getToken(token.Cookie)
  1618. if assert.Error(t, err) {
  1619. assert.Contains(t, err.Error(), "unable to get the token for the specified session")
  1620. }
  1621. // add an expired token
  1622. token.UsedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-24 * time.Hour))
  1623. session := dataprovider.Session{
  1624. Key: token.Cookie,
  1625. Data: token,
  1626. Type: dataprovider.SessionTypeOIDCToken,
  1627. Timestamp: token.UsedAt + tokenDeleteInterval,
  1628. }
  1629. err = dataprovider.AddSharedSession(session)
  1630. assert.NoError(t, err)
  1631. _, err = mgr.getToken(token.Cookie)
  1632. if assert.Error(t, err) {
  1633. assert.Contains(t, err.Error(), "token is too old")
  1634. }
  1635. mgr.cleanup()
  1636. _, err = mgr.getToken(token.Cookie)
  1637. if assert.Error(t, err) {
  1638. assert.Contains(t, err.Error(), "unable to get the token for the specified session")
  1639. }
  1640. // adding a session without a key should fail
  1641. session.Key = ""
  1642. err = dataprovider.AddSharedSession(session)
  1643. if assert.Error(t, err) {
  1644. assert.Contains(t, err.Error(), "unable to save a session with an empty key")
  1645. }
  1646. session.Key = xid.New().String()
  1647. session.Type = 1000
  1648. err = dataprovider.AddSharedSession(session)
  1649. if assert.Error(t, err) {
  1650. assert.Contains(t, err.Error(), "invalid session type")
  1651. }
  1652. dbMgr, ok := mgr.(*dbOIDCManager)
  1653. if assert.True(t, ok) {
  1654. _, err = dbMgr.decodePendingAuthData(2)
  1655. assert.Error(t, err)
  1656. _, err = dbMgr.decodeTokenData(true)
  1657. assert.Error(t, err)
  1658. }
  1659. }
  1660. func getTestOIDCServer() *httpdServer {
  1661. return &httpdServer{
  1662. binding: Binding{
  1663. OIDC: OIDC{
  1664. ClientID: "sftpgo-client",
  1665. ClientSecret: "jRsmE0SWnuZjP7djBqNq0mrf8QN77j2c",
  1666. ConfigURL: fmt.Sprintf("http://%v/auth/realms/sftpgo", oidcMockAddr),
  1667. RedirectBaseURL: "http://127.0.0.1:8081/",
  1668. UsernameField: "preferred_username",
  1669. RoleField: "sftpgo_role",
  1670. ImplicitRoles: false,
  1671. Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
  1672. CustomFields: nil,
  1673. Debug: true,
  1674. },
  1675. },
  1676. enableWebAdmin: true,
  1677. enableWebClient: true,
  1678. }
  1679. }
  1680. func getPreLoginScriptContent(user dataprovider.User, nonJSONResponse bool) []byte {
  1681. content := []byte("#!/bin/sh\n\n")
  1682. if nonJSONResponse {
  1683. content = append(content, []byte("echo 'text response'\n")...)
  1684. return content
  1685. }
  1686. if len(user.Username) > 0 {
  1687. u, _ := json.Marshal(user)
  1688. content = append(content, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
  1689. }
  1690. return content
  1691. }