oidc.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. package httpd
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/coreos/go-oidc/v3/oidc"
  12. "github.com/rs/xid"
  13. "golang.org/x/oauth2"
  14. "github.com/drakkan/sftpgo/v2/common"
  15. "github.com/drakkan/sftpgo/v2/dataprovider"
  16. "github.com/drakkan/sftpgo/v2/httpclient"
  17. "github.com/drakkan/sftpgo/v2/logger"
  18. "github.com/drakkan/sftpgo/v2/util"
  19. )
  20. const (
  21. oidcCookieKey = "oidc"
  22. authStateValidity = 1 * 60 * 1000 // 1 minute
  23. tokenUpdateInterval = 3 * 60 * 1000 // 3 minutes
  24. tokenDeleteInterval = 2 * 3600 * 1000 // 2 hours
  25. )
  26. var (
  27. oidcTokenKey = &contextKey{"OIDC token key"}
  28. oidcGeneratedToken = &contextKey{"OIDC generated token"}
  29. oidcMgr *oidcManager
  30. )
  31. func init() {
  32. oidcMgr = &oidcManager{
  33. pendingAuths: make(map[string]oidcPendingAuth),
  34. tokens: make(map[string]oidcToken),
  35. lastCleanup: time.Now(),
  36. }
  37. }
  38. // OAuth2Config defines an interface for OAuth2 methods, so we can mock them
  39. type OAuth2Config interface {
  40. AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string
  41. Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error)
  42. TokenSource(ctx context.Context, t *oauth2.Token) oauth2.TokenSource
  43. }
  44. // OIDCTokenVerifier defines an interface for OpenID token verifier, so we can mock them
  45. type OIDCTokenVerifier interface {
  46. Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error)
  47. }
  48. // OIDC defines the OpenID Connect configuration
  49. type OIDC struct {
  50. // ClientID is the application's ID
  51. ClientID string `json:"client_id" mapstructure:"client_id"`
  52. // ClientSecret is the application's secret
  53. ClientSecret string `json:"client_secret" mapstructure:"client_secret"`
  54. // ConfigURL is the identifier for the service.
  55. // SFTPGo will try to retrieve the provider configuration on startup and then
  56. // will refuse to start if it fails to connect to the specified URL
  57. ConfigURL string `json:"config_url" mapstructure:"config_url"`
  58. // RedirectBaseURL is the base URL to redirect to after OpenID authentication.
  59. // The suffix "/web/oidc/redirect" will be added to this base URL, adding also the
  60. // "web_root" if configured
  61. RedirectBaseURL string `json:"redirect_base_url" mapstructure:"redirect_base_url"`
  62. // ID token claims field to map to the SFTPGo username
  63. UsernameField string `json:"username_field" mapstructure:"username_field"`
  64. // Optional ID token claims field to map to a SFTPGo role.
  65. // If the defined ID token claims field is set to "admin" the authenticated user
  66. // is mapped to an SFTPGo admin.
  67. // You don't need to specify this field if you want to use OpenID only for the
  68. // Web Client UI
  69. RoleField string `json:"role_field" mapstructure:"role_field"`
  70. provider *oidc.Provider
  71. verifier OIDCTokenVerifier
  72. providerLogoutURL string
  73. oauth2Config OAuth2Config
  74. }
  75. func (o *OIDC) isEnabled() bool {
  76. return o.provider != nil
  77. }
  78. func (o *OIDC) hasRoles() bool {
  79. return o.isEnabled() && o.RoleField != ""
  80. }
  81. func (o *OIDC) getRedirectURL() string {
  82. url := o.RedirectBaseURL
  83. if strings.HasSuffix(o.RedirectBaseURL, "/") {
  84. url = strings.TrimSuffix(o.RedirectBaseURL, "/")
  85. }
  86. url += webOIDCRedirectPath
  87. logger.Debug(logSender, "", "oidc redirect URL: %#v", url)
  88. return url
  89. }
  90. func (o *OIDC) initialize() error {
  91. if o.ConfigURL == "" {
  92. return nil
  93. }
  94. if o.UsernameField == "" {
  95. return errors.New("oidc: username field cannot be empty")
  96. }
  97. if o.RedirectBaseURL == "" {
  98. return errors.New("oidc: redirect base URL cannot be empty")
  99. }
  100. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  101. defer cancel()
  102. provider, err := oidc.NewProvider(ctx, o.ConfigURL)
  103. if err != nil {
  104. return fmt.Errorf("oidc: unable to initialize provider for URL %#v: %w", o.ConfigURL, err)
  105. }
  106. claims := make(map[string]interface{})
  107. // we cannot get an error here because the response body was already parsed as JSON
  108. // on provider creation
  109. provider.Claims(&claims) //nolint:errcheck
  110. endSessionEndPoint, ok := claims["end_session_endpoint"]
  111. if ok {
  112. if val, ok := endSessionEndPoint.(string); ok {
  113. o.providerLogoutURL = val
  114. logger.Debug(logSender, "", "oidc end session endpoint %#v", o.providerLogoutURL)
  115. }
  116. }
  117. o.provider = provider
  118. o.verifier = provider.Verifier(&oidc.Config{
  119. ClientID: o.ClientID,
  120. })
  121. o.oauth2Config = &oauth2.Config{
  122. ClientID: o.ClientID,
  123. ClientSecret: o.ClientSecret,
  124. Endpoint: o.provider.Endpoint(),
  125. RedirectURL: o.getRedirectURL(),
  126. Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
  127. }
  128. return nil
  129. }
  130. type oidcPendingAuth struct {
  131. State string
  132. Nonce string
  133. Audience tokenAudience
  134. IssueAt int64
  135. }
  136. func newOIDCPendingAuth(audience tokenAudience) oidcPendingAuth {
  137. return oidcPendingAuth{
  138. State: xid.New().String(),
  139. Nonce: xid.New().String(),
  140. Audience: audience,
  141. IssueAt: util.GetTimeAsMsSinceEpoch(time.Now()),
  142. }
  143. }
  144. type oidcToken struct {
  145. AccessToken string `json:"access_token"`
  146. TokenType string `json:"token_type,omitempty"`
  147. RefreshToken string `json:"refresh_token,omitempty"`
  148. ExpiresAt int64 `json:"expires_at,omitempty"`
  149. SessionID string `json:"session_id"`
  150. IDToken string `json:"id_token"`
  151. Nonce string `json:"nonce"`
  152. Username string `json:"username"`
  153. Permissions []string `json:"permissions"`
  154. Role string `json:"role"`
  155. Cookie string `json:"cookie"`
  156. UsedAt int64 `json:"used_at"`
  157. }
  158. func (t *oidcToken) parseClaims(claims map[string]interface{}, usernameField, roleField string) error {
  159. getClaimsFields := func() []string {
  160. keys := make([]string, 0, len(claims))
  161. for k := range claims {
  162. keys = append(keys, k)
  163. }
  164. return keys
  165. }
  166. username, ok := claims[usernameField].(string)
  167. if !ok || username == "" {
  168. logger.Warn(logSender, "", "username field %#v not found, claims fields: %+v", usernameField, getClaimsFields())
  169. return errors.New("no username field")
  170. }
  171. t.Username = username
  172. if roleField != "" {
  173. role, ok := claims[roleField].(string)
  174. if ok {
  175. t.Role = role
  176. }
  177. }
  178. sid, ok := claims["sid"].(string)
  179. if ok {
  180. t.SessionID = sid
  181. }
  182. return nil
  183. }
  184. func (t *oidcToken) isAdmin() bool {
  185. return t.Role == "admin"
  186. }
  187. func (t *oidcToken) isExpired() bool {
  188. if t.ExpiresAt == 0 {
  189. return false
  190. }
  191. return t.ExpiresAt < util.GetTimeAsMsSinceEpoch(time.Now())
  192. }
  193. func (t *oidcToken) refresh(config OAuth2Config, verifier OIDCTokenVerifier) error {
  194. if t.RefreshToken == "" {
  195. logger.Debug(logSender, "", "refresh token not set, unable to refresh cookie %#v", t.Cookie)
  196. return errors.New("refresh token not set")
  197. }
  198. oauth2Token := oauth2.Token{
  199. AccessToken: t.AccessToken,
  200. TokenType: t.TokenType,
  201. RefreshToken: t.RefreshToken,
  202. }
  203. if t.ExpiresAt > 0 {
  204. oauth2Token.Expiry = util.GetTimeFromMsecSinceEpoch(t.ExpiresAt)
  205. }
  206. ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
  207. defer cancel()
  208. newToken, err := config.TokenSource(ctx, &oauth2Token).Token()
  209. if err != nil {
  210. logger.Debug(logSender, "", "unable to refresh token for cookie %#v: %v", t.Cookie, err)
  211. return err
  212. }
  213. rawIDToken, ok := newToken.Extra("id_token").(string)
  214. if !ok {
  215. logger.Debug(logSender, "", "the refreshed token has no id token, cookie %#v", t.Cookie)
  216. return errors.New("the refreshed token has no id token")
  217. }
  218. t.AccessToken = newToken.AccessToken
  219. t.TokenType = newToken.TokenType
  220. t.RefreshToken = newToken.RefreshToken
  221. t.IDToken = rawIDToken
  222. if !newToken.Expiry.IsZero() {
  223. t.ExpiresAt = util.GetTimeAsMsSinceEpoch(newToken.Expiry)
  224. } else {
  225. t.ExpiresAt = 0
  226. }
  227. idToken, err := verifier.Verify(ctx, rawIDToken)
  228. if err != nil {
  229. logger.Debug(logSender, "", "unable to verify refreshed id token for cookie %#v: %v", t.Cookie, err)
  230. return err
  231. }
  232. if idToken.Nonce != t.Nonce {
  233. logger.Debug(logSender, "", "unable to verify refreshed id token for cookie %#v: nonce mismatch", t.Cookie)
  234. return errors.New("the refreshed token nonce mismatch")
  235. }
  236. claims := make(map[string]interface{})
  237. err = idToken.Claims(&claims)
  238. if err != nil {
  239. logger.Debug(logSender, "", "unable to get refreshed id token claims for cookie %#v: %v", t.Cookie, err)
  240. return err
  241. }
  242. sid, ok := claims["sid"].(string)
  243. if ok {
  244. t.SessionID = sid
  245. }
  246. logger.Debug(logSender, "", "oidc token refreshed for user %#v, cookie %#v", t.Username, t.Cookie)
  247. oidcMgr.addToken(*t)
  248. return nil
  249. }
  250. func (t *oidcToken) getUser(r *http.Request) error {
  251. if t.isAdmin() {
  252. admin, err := dataprovider.AdminExists(t.Username)
  253. if err != nil {
  254. return err
  255. }
  256. if err := admin.CanLogin(util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
  257. return err
  258. }
  259. t.Permissions = admin.Permissions
  260. dataprovider.UpdateAdminLastLogin(&admin)
  261. return nil
  262. }
  263. ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
  264. user, err := dataprovider.GetUserAfterIDPAuth(t.Username, ipAddr, common.ProtocolOIDC)
  265. if err != nil {
  266. return err
  267. }
  268. if err := common.Config.ExecutePostConnectHook(ipAddr, common.ProtocolOIDC); err != nil {
  269. updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, err)
  270. return fmt.Errorf("access denied by post connect hook: %w", err)
  271. }
  272. if err := user.CheckLoginConditions(); err != nil {
  273. updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, err)
  274. return err
  275. }
  276. connectionID := fmt.Sprintf("%v_%v", common.ProtocolOIDC, xid.New().String())
  277. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  278. updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, err)
  279. return err
  280. }
  281. defer user.CloseFs() //nolint:errcheck
  282. err = user.CheckFsRoot(connectionID)
  283. if err != nil {
  284. logger.Warn(logSender, connectionID, "unable to check fs root: %v", err)
  285. updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, common.ErrInternalFailure)
  286. return err
  287. }
  288. updateLoginMetrics(&user, dataprovider.LoginMethodIDP, ipAddr, nil)
  289. dataprovider.UpdateLastLogin(&user)
  290. t.Permissions = user.Filters.WebClient
  291. return nil
  292. }
  293. type oidcManager struct {
  294. authMutex sync.RWMutex
  295. pendingAuths map[string]oidcPendingAuth
  296. tokenMutex sync.RWMutex
  297. tokens map[string]oidcToken
  298. lastCleanup time.Time
  299. }
  300. func (o *oidcManager) addPendingAuth(pendingAuth oidcPendingAuth) {
  301. o.authMutex.Lock()
  302. o.pendingAuths[pendingAuth.State] = pendingAuth
  303. o.authMutex.Unlock()
  304. o.checkCleanup()
  305. }
  306. func (o *oidcManager) removePendingAuth(key string) {
  307. o.authMutex.Lock()
  308. defer o.authMutex.Unlock()
  309. delete(o.pendingAuths, key)
  310. }
  311. func (o *oidcManager) getPendingAuth(state string) (oidcPendingAuth, error) {
  312. o.authMutex.RLock()
  313. defer o.authMutex.RUnlock()
  314. authReq, ok := o.pendingAuths[state]
  315. if !ok {
  316. return oidcPendingAuth{}, errors.New("oidc: no auth request found for the specified state")
  317. }
  318. diff := util.GetTimeAsMsSinceEpoch(time.Now()) - authReq.IssueAt
  319. if diff > authStateValidity {
  320. return oidcPendingAuth{}, errors.New("oidc: auth request is too old")
  321. }
  322. return authReq, nil
  323. }
  324. func (o *oidcManager) addToken(token oidcToken) {
  325. o.tokenMutex.Lock()
  326. token.UsedAt = util.GetTimeAsMsSinceEpoch(time.Now())
  327. o.tokens[token.Cookie] = token
  328. o.tokenMutex.Unlock()
  329. o.checkCleanup()
  330. }
  331. func (o *oidcManager) getToken(cookie string) (oidcToken, error) {
  332. o.tokenMutex.RLock()
  333. defer o.tokenMutex.RUnlock()
  334. token, ok := o.tokens[cookie]
  335. if !ok {
  336. return oidcToken{}, errors.New("oidc: no token found for the specified session")
  337. }
  338. return token, nil
  339. }
  340. func (o *oidcManager) removeToken(cookie string) {
  341. o.tokenMutex.Lock()
  342. defer o.tokenMutex.Unlock()
  343. delete(o.tokens, cookie)
  344. }
  345. func (o *oidcManager) updateTokenUsage(token oidcToken) {
  346. diff := util.GetTimeAsMsSinceEpoch(time.Now()) - token.UsedAt
  347. if diff > tokenUpdateInterval {
  348. o.addToken(token)
  349. }
  350. }
  351. func (o *oidcManager) checkCleanup() {
  352. o.authMutex.RLock()
  353. needCleanup := o.lastCleanup.Add(20 * time.Minute).Before(time.Now())
  354. o.authMutex.RUnlock()
  355. if needCleanup {
  356. o.authMutex.Lock()
  357. o.lastCleanup = time.Now()
  358. o.authMutex.Unlock()
  359. o.cleanupAuthRequests()
  360. o.cleanupTokens()
  361. }
  362. }
  363. func (o *oidcManager) cleanupAuthRequests() {
  364. o.authMutex.Lock()
  365. defer o.authMutex.Unlock()
  366. for k, auth := range o.pendingAuths {
  367. diff := util.GetTimeAsMsSinceEpoch(time.Now()) - auth.IssueAt
  368. // remove old pending auth requests
  369. if diff < 0 || diff > authStateValidity {
  370. delete(o.pendingAuths, k)
  371. }
  372. }
  373. }
  374. func (o *oidcManager) cleanupTokens() {
  375. o.tokenMutex.Lock()
  376. defer o.tokenMutex.Unlock()
  377. for k, token := range o.tokens {
  378. diff := util.GetTimeAsMsSinceEpoch(time.Now()) - token.UsedAt
  379. // remove tokens unused from more than 1 hour
  380. if diff > tokenDeleteInterval {
  381. delete(o.tokens, k)
  382. }
  383. }
  384. }
  385. func (s *httpdServer) validateOIDCToken(w http.ResponseWriter, r *http.Request, isAdmin bool) (oidcToken, error) {
  386. doRedirect := func() {
  387. removeOIDCCookie(w, r)
  388. if isAdmin {
  389. http.Redirect(w, r, webAdminLoginPath, http.StatusFound)
  390. return
  391. }
  392. http.Redirect(w, r, webClientLoginPath, http.StatusFound)
  393. }
  394. cookie, err := r.Cookie(oidcCookieKey)
  395. if err != nil {
  396. logger.Debug(logSender, "", "no oidc cookie, redirecting to login page")
  397. doRedirect()
  398. return oidcToken{}, errInvalidToken
  399. }
  400. token, err := oidcMgr.getToken(cookie.Value)
  401. if err != nil {
  402. logger.Debug(logSender, "", "error getting oidc token associated with cookie %#v: %v", cookie.Value, err)
  403. doRedirect()
  404. return oidcToken{}, errInvalidToken
  405. }
  406. if token.isExpired() {
  407. logger.Debug(logSender, "", "oidc token associated with cookie %#v is expired", token.Cookie)
  408. if err = token.refresh(s.binding.OIDC.oauth2Config, s.binding.OIDC.verifier); err != nil {
  409. setFlashMessage(w, r, "Your OpenID token is expired, please log-in again")
  410. doRedirect()
  411. return oidcToken{}, errInvalidToken
  412. }
  413. } else {
  414. oidcMgr.updateTokenUsage(token)
  415. }
  416. if isAdmin {
  417. if !token.isAdmin() {
  418. logger.Debug(logSender, "", "oidc token associated with cookie %#v is not valid for admin users", token.Cookie)
  419. setFlashMessage(w, r, "Your OpenID token is not valid for the SFTPGo Web Admin UI. Please logout from your OpenID server and log-in as an SFTPGo admin")
  420. doRedirect()
  421. return oidcToken{}, errInvalidToken
  422. }
  423. return token, nil
  424. }
  425. if token.isAdmin() {
  426. logger.Debug(logSender, "", "oidc token associated with cookie %#v is valid for admin users", token.Cookie)
  427. setFlashMessage(w, r, "Your OpenID token is not valid for the SFTPGo Web Client UI. Please logout from your OpenID server and log-in as an SFTPGo user")
  428. doRedirect()
  429. return oidcToken{}, errInvalidToken
  430. }
  431. return token, nil
  432. }
  433. func (s *httpdServer) oidcTokenAuthenticator(audience tokenAudience) func(next http.Handler) http.Handler {
  434. return func(next http.Handler) http.Handler {
  435. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  436. if canSkipOIDCValidation(r) {
  437. next.ServeHTTP(w, r)
  438. return
  439. }
  440. token, err := s.validateOIDCToken(w, r, audience == tokenAudienceWebAdmin)
  441. if err != nil {
  442. return
  443. }
  444. jwtTokenClaims := jwtTokenClaims{
  445. Username: token.Username,
  446. Permissions: token.Permissions,
  447. }
  448. _, tokenString, err := jwtTokenClaims.createToken(s.tokenAuth, audience)
  449. if err != nil {
  450. setFlashMessage(w, r, "Unable to create cookie")
  451. if audience == tokenAudienceWebAdmin {
  452. http.Redirect(w, r, webAdminLoginPath, http.StatusFound)
  453. } else {
  454. http.Redirect(w, r, webClientLoginPath, http.StatusFound)
  455. }
  456. return
  457. }
  458. ctx := context.WithValue(r.Context(), oidcTokenKey, token.Cookie)
  459. ctx = context.WithValue(ctx, oidcGeneratedToken, tokenString)
  460. next.ServeHTTP(w, r.WithContext(ctx))
  461. })
  462. }
  463. }
  464. func (s *httpdServer) handleWebAdminOIDCLogin(w http.ResponseWriter, r *http.Request) {
  465. s.oidcLoginRedirect(w, r, tokenAudienceWebAdmin)
  466. }
  467. func (s *httpdServer) handleWebClientOIDCLogin(w http.ResponseWriter, r *http.Request) {
  468. s.oidcLoginRedirect(w, r, tokenAudienceWebClient)
  469. }
  470. func (s *httpdServer) oidcLoginRedirect(w http.ResponseWriter, r *http.Request, audience tokenAudience) {
  471. pendingAuth := newOIDCPendingAuth(audience)
  472. oidcMgr.addPendingAuth(pendingAuth)
  473. http.Redirect(w, r, s.binding.OIDC.oauth2Config.AuthCodeURL(pendingAuth.State,
  474. oidc.Nonce(pendingAuth.Nonce)), http.StatusFound)
  475. }
  476. func (s *httpdServer) handleOIDCRedirect(w http.ResponseWriter, r *http.Request) {
  477. state := r.URL.Query().Get("state")
  478. authReq, err := oidcMgr.getPendingAuth(state)
  479. if err != nil {
  480. logger.Debug(logSender, "", "oidc authentication state did not match")
  481. s.renderClientMessagePage(w, r, "Invalid authentication request", "Authentication state did not match",
  482. http.StatusBadRequest, nil, "")
  483. return
  484. }
  485. oidcMgr.removePendingAuth(state)
  486. doRedirect := func() {
  487. if authReq.Audience == tokenAudienceWebAdmin {
  488. http.Redirect(w, r, webAdminLoginPath, http.StatusFound)
  489. return
  490. }
  491. http.Redirect(w, r, webClientLoginPath, http.StatusFound)
  492. }
  493. doLogout := func(rawIDToken string) {
  494. s.logoutFromOIDCOP(rawIDToken)
  495. }
  496. ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
  497. defer cancel()
  498. oauth2Token, err := s.binding.OIDC.oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
  499. if err != nil {
  500. logger.Debug(logSender, "", "failed to exchange oidc token: %v", err)
  501. setFlashMessage(w, r, "Failed to exchange OpenID token")
  502. doRedirect()
  503. return
  504. }
  505. rawIDToken, ok := oauth2Token.Extra("id_token").(string)
  506. if !ok {
  507. logger.Debug(logSender, "", "no id_token field in OAuth2 OpenID token")
  508. setFlashMessage(w, r, "No id_token field in OAuth2 OpenID token")
  509. doRedirect()
  510. return
  511. }
  512. idToken, err := s.binding.OIDC.verifier.Verify(ctx, rawIDToken)
  513. if err != nil {
  514. logger.Debug(logSender, "", "failed to verify oidc token: %v", err)
  515. setFlashMessage(w, r, "Failed to verify OpenID token")
  516. doRedirect()
  517. doLogout(rawIDToken)
  518. return
  519. }
  520. if idToken.Nonce != authReq.Nonce {
  521. logger.Debug(logSender, "", "oidc authentication nonce did not match")
  522. setFlashMessage(w, r, "OpenID authentication nonce did not match")
  523. doRedirect()
  524. doLogout(rawIDToken)
  525. return
  526. }
  527. claims := make(map[string]interface{})
  528. err = idToken.Claims(&claims)
  529. if err != nil {
  530. logger.Debug(logSender, "", "unable to get oidc token claims: %v", err)
  531. setFlashMessage(w, r, "Unable to get OpenID token claims")
  532. doRedirect()
  533. doLogout(rawIDToken)
  534. return
  535. }
  536. token := oidcToken{
  537. AccessToken: oauth2Token.AccessToken,
  538. TokenType: oauth2Token.TokenType,
  539. RefreshToken: oauth2Token.RefreshToken,
  540. IDToken: rawIDToken,
  541. Nonce: idToken.Nonce,
  542. Cookie: xid.New().String(),
  543. }
  544. if !oauth2Token.Expiry.IsZero() {
  545. token.ExpiresAt = util.GetTimeAsMsSinceEpoch(oauth2Token.Expiry)
  546. }
  547. if err = token.parseClaims(claims, s.binding.OIDC.UsernameField, s.binding.OIDC.RoleField); err != nil {
  548. logger.Debug(logSender, "", "unable to parse oidc token claims: %v", err)
  549. setFlashMessage(w, r, fmt.Sprintf("Unable to parse OpenID token claims: %v", err))
  550. doRedirect()
  551. doLogout(rawIDToken)
  552. return
  553. }
  554. switch authReq.Audience {
  555. case tokenAudienceWebAdmin:
  556. if !token.isAdmin() {
  557. logger.Debug(logSender, "", "wrong oidc token role, the mapped user is not an SFTPGo admin")
  558. setFlashMessage(w, r, "Wrong OpenID role, the logged in user is not an SFTPGo admin")
  559. doRedirect()
  560. doLogout(rawIDToken)
  561. return
  562. }
  563. case tokenAudienceWebClient:
  564. if token.isAdmin() {
  565. logger.Debug(logSender, "", "wrong oidc token role, the mapped user is an SFTPGo admin")
  566. setFlashMessage(w, r, "Wrong OpenID role, the logged in user is an SFTPGo admin")
  567. doRedirect()
  568. doLogout(rawIDToken)
  569. return
  570. }
  571. }
  572. err = token.getUser(r)
  573. if err != nil {
  574. logger.Debug(logSender, "", "unable to get the sftpgo user associated with oidc token: %v", err)
  575. setFlashMessage(w, r, "Unable to get the user associated with the OpenID token")
  576. doRedirect()
  577. doLogout(rawIDToken)
  578. return
  579. }
  580. loginOIDCUser(w, r, token)
  581. }
  582. func loginOIDCUser(w http.ResponseWriter, r *http.Request, token oidcToken) {
  583. oidcMgr.addToken(token)
  584. cookie := http.Cookie{
  585. Name: oidcCookieKey,
  586. Value: token.Cookie,
  587. Path: "/",
  588. HttpOnly: true,
  589. Secure: isTLS(r),
  590. SameSite: http.SameSiteLaxMode,
  591. }
  592. // we don't set a cookie expiration so we can refresh the token without setting a new cookie
  593. // the cookie will be invalidated on browser close
  594. http.SetCookie(w, &cookie)
  595. if token.isAdmin() {
  596. http.Redirect(w, r, webUsersPath, http.StatusFound)
  597. return
  598. }
  599. http.Redirect(w, r, webClientFilesPath, http.StatusFound)
  600. }
  601. func (s *httpdServer) logoutOIDCUser(w http.ResponseWriter, r *http.Request) {
  602. if oidcKey, ok := r.Context().Value(oidcTokenKey).(string); ok {
  603. removeOIDCCookie(w, r)
  604. token, err := oidcMgr.getToken(oidcKey)
  605. if err == nil {
  606. s.logoutFromOIDCOP(token.IDToken)
  607. }
  608. oidcMgr.removeToken(oidcKey)
  609. }
  610. }
  611. func (s *httpdServer) logoutFromOIDCOP(idToken string) {
  612. if s.binding.OIDC.providerLogoutURL == "" {
  613. logger.Debug(logSender, "", "oidc: provider logout URL not set, unable to logout from the OP")
  614. return
  615. }
  616. go s.doOIDCFromLogout(idToken)
  617. }
  618. func (s *httpdServer) doOIDCFromLogout(idToken string) {
  619. logoutURL, err := url.Parse(s.binding.OIDC.providerLogoutURL)
  620. if err != nil {
  621. logger.Warn(logSender, "", "oidc: unable to parse logout URL: %v", err)
  622. return
  623. }
  624. query := logoutURL.Query()
  625. if idToken != "" {
  626. query.Set("id_token_hint", idToken)
  627. }
  628. logoutURL.RawQuery = query.Encode()
  629. resp, err := httpclient.RetryableGet(logoutURL.String())
  630. if err != nil {
  631. logger.Warn(logSender, "", "oidc: error calling logout URL %#v: %v", logoutURL.String(), err)
  632. return
  633. }
  634. defer resp.Body.Close()
  635. logger.Debug(logSender, "", "oidc: logout url response code %v", resp.StatusCode)
  636. }
  637. func removeOIDCCookie(w http.ResponseWriter, r *http.Request) {
  638. http.SetCookie(w, &http.Cookie{
  639. Name: oidcCookieKey,
  640. Value: "",
  641. Path: "/",
  642. Expires: time.Unix(0, 0),
  643. MaxAge: -1,
  644. HttpOnly: true,
  645. Secure: isTLS(r),
  646. SameSite: http.SameSiteLaxMode,
  647. })
  648. }
  649. // canSkipOIDCValidation returns true if there is no OIDC cookie but a jwt cookie is set
  650. // and so we check if the user is logged in using a built-in user
  651. func canSkipOIDCValidation(r *http.Request) bool {
  652. _, err := r.Cookie(oidcCookieKey)
  653. if err != nil {
  654. _, err = r.Cookie(jwtCookieKey)
  655. return err == nil
  656. }
  657. return false
  658. }
  659. func isLoggedInWithOIDC(r *http.Request) bool {
  660. _, ok := r.Context().Value(oidcTokenKey).(string)
  661. return ok
  662. }