Explorar o código

oidc: update user after token refresh

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Nicola Murino %!s(int64=2) %!d(string=hai) anos
pai
achega
6c7b3ac5bb
Modificáronse 4 ficheiros con 155 adicións e 19 borrados
  1. 3 4
      go.mod
  2. 6 6
      go.sum
  3. 34 2
      internal/httpd/oidc.go
  4. 112 7
      internal/httpd/oidc_test.go

+ 3 - 4
go.mod

@@ -20,7 +20,7 @@ require (
 	github.com/cockroachdb/cockroach-go/v2 v2.2.16
 	github.com/coreos/go-oidc/v3 v3.4.0
 	github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
-	github.com/fclairamb/ftpserverlib v0.19.1
+	github.com/fclairamb/ftpserverlib v0.19.2-0.20220922051837-cde05ddf9fe6
 	github.com/fclairamb/go-log v0.4.1
 	github.com/go-acme/lego/v4 v4.8.0
 	github.com/go-chi/chi/v5 v5.0.8-0.20220512131524-9e71a0d4b3d6
@@ -70,7 +70,7 @@ require (
 	golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
 	golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8
 	golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45
-	google.golang.org/api v0.96.0
+	google.golang.org/api v0.97.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 )
 
@@ -156,7 +156,7 @@ require (
 	golang.org/x/tools v0.1.12 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 // indirect
+	google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect
 	google.golang.org/grpc v1.49.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
@@ -166,7 +166,6 @@ require (
 )
 
 replace (
-	github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20220917142547-394d5e183aeb
 	github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
 	golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220831070132-e3c36f2ab82b
 	golang.org/x/net => github.com/drakkan/net v0.0.0-20220913160159-a08dc61b7895

+ 6 - 6
go.sum

@@ -268,8 +268,6 @@ github.com/drakkan/crypto v0.0.0-20220831070132-e3c36f2ab82b h1:kCNBtUFKfhiUaE1Z
 github.com/drakkan/crypto v0.0.0-20220831070132-e3c36f2ab82b/go.mod h1:SiM6ypd8Xu1xldObYtbDztuUU7xUzMnUULfphXFZmro=
 github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
 github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
-github.com/drakkan/ftpserverlib v0.0.0-20220917142547-394d5e183aeb h1:sd63fxu7eKejDU0fBiGvVejeEEXNcGv6SVmXrFsDaKM=
-github.com/drakkan/ftpserverlib v0.0.0-20220917142547-394d5e183aeb/go.mod h1:Nwsxl2ZzyPiSCgB1rZGnEscTenwkxPhCn1D+Hm/k9JA=
 github.com/drakkan/net v0.0.0-20220913160159-a08dc61b7895 h1:YZkDIISo8YO7PAOX85GYxGCayjBqAutIAjL+XsdEgkc=
 github.com/drakkan/net v0.0.0-20220913160159-a08dc61b7895/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
@@ -286,6 +284,8 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fclairamb/ftpserverlib v0.19.2-0.20220922051837-cde05ddf9fe6 h1:WdhM0yDKdtSD+cqWHAMLMTwvUvmzy36eI3Ow8emZmn0=
+github.com/fclairamb/ftpserverlib v0.19.2-0.20220922051837-cde05ddf9fe6/go.mod h1:7pR5Ckeygw3T006z1ND6HYSbJz+fTvkFAXlF6snW4yI=
 github.com/fclairamb/go-log v0.4.1 h1:rLtdSG9x2pK41AIAnE8WYpl05xBJfw1ZyYxZaXFcBsM=
 github.com/fclairamb/go-log v0.4.1/go.mod h1:sw1KvnkZ4wKCYkvy4SL3qVZcJSWFP8Ure4pM3z+KNn4=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@@ -1123,8 +1123,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69
 google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
 google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
 google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
-google.golang.org/api v0.96.0 h1:F60cuQPJq7K7FzsxMYHAUJSiXh2oKctHxBMbDygxhfM=
-google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.97.0 h1:x/vEL1XDF/2V4xzdNgFPaKHluRESo2aTsL7QzHnBtGQ=
+google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1229,8 +1229,8 @@ google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP
 google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
-google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 h1:mmbq5q8M1t7dhkLw320YK4PsOXm6jdnUAkErImaIqOg=
-google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
+google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ=
+google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

+ 34 - 2
internal/httpd/oidc.go

@@ -308,7 +308,7 @@ func (t *oidcToken) isExpired() bool {
 	return t.ExpiresAt < util.GetTimeAsMsSinceEpoch(time.Now())
 }
 
-func (t *oidcToken) refresh(config OAuth2Config, verifier OIDCTokenVerifier) error {
+func (t *oidcToken) refresh(config OAuth2Config, verifier OIDCTokenVerifier, r *http.Request) error {
 	if t.RefreshToken == "" {
 		logger.Debug(logSender, "", "refresh token not set, unable to refresh cookie %#v", t.Cookie)
 		return errors.New("refresh token not set")
@@ -363,12 +363,44 @@ func (t *oidcToken) refresh(config OAuth2Config, verifier OIDCTokenVerifier) err
 	if ok {
 		t.SessionID = sid
 	}
+	err = t.refreshUser(r)
+	if err != nil {
+		logger.Debug(logSender, "", "unable to refresh user after token refresh for cookie %#v: %v", t.Cookie, err)
+		return err
+	}
 	logger.Debug(logSender, "", "oidc token refreshed for user %#v, cookie %#v", t.Username, t.Cookie)
 	oidcMgr.addToken(*t)
 
 	return nil
 }
 
+func (t *oidcToken) refreshUser(r *http.Request) error {
+	if t.isAdmin() {
+		admin, err := dataprovider.AdminExists(t.Username)
+		if err != nil {
+			return err
+		}
+		if err := admin.CanLogin(util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
+			return err
+		}
+		t.Permissions = admin.Permissions
+		t.HideUserPageSections = admin.Filters.Preferences.HideUserPageSections
+		return nil
+	}
+	user, err := dataprovider.GetUserWithGroupSettings(t.Username)
+	if err != nil {
+		return err
+	}
+	if err := user.CheckLoginConditions(); err != nil {
+		return err
+	}
+	if err := checkHTTPClientUser(&user, r, xid.New().String(), true); err != nil {
+		return err
+	}
+	t.Permissions = user.Filters.WebClient
+	return nil
+}
+
 func (t *oidcToken) getUser(r *http.Request) error {
 	if t.isAdmin() {
 		admin, err := dataprovider.AdminExists(t.Username)
@@ -438,7 +470,7 @@ func (s *httpdServer) validateOIDCToken(w http.ResponseWriter, r *http.Request,
 	}
 	if token.isExpired() {
 		logger.Debug(logSender, "", "oidc token associated with cookie %#v is expired", token.Cookie)
-		if err = token.refresh(s.binding.OIDC.oauth2Config, s.binding.OIDC.verifier); err != nil {
+		if err = token.refresh(s.binding.OIDC.oauth2Config, s.binding.OIDC.verifier, r); err != nil {
 			setFlashMessage(w, r, "Your OpenID token is expired, please log-in again")
 			doRedirect()
 			return oidcToken{}, errInvalidToken

+ 112 - 7
internal/httpd/oidc_test.go

@@ -528,12 +528,16 @@ func TestOIDCLoginLogout(t *testing.T) {
 func TestOIDCRefreshToken(t *testing.T) {
 	oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
 	require.True(t, ok)
+	r, err := http.NewRequest(http.MethodGet, webUsersPath, nil)
+	assert.NoError(t, err)
 	token := oidcToken{
 		Cookie:      xid.New().String(),
 		AccessToken: xid.New().String(),
 		TokenType:   "Bearer",
 		ExpiresAt:   util.GetTimeAsMsSinceEpoch(time.Now().Add(-1 * time.Minute)),
 		Nonce:       xid.New().String(),
+		Role:        adminRoleFieldValue,
+		Username:    defaultAdminUsername,
 	}
 	config := mockOAuth2Config{
 		tokenSource: &mockTokenSource{
@@ -543,12 +547,12 @@ func TestOIDCRefreshToken(t *testing.T) {
 	verifier := mockOIDCVerifier{
 		err: common.ErrGenericFailure,
 	}
-	err := token.refresh(&config, &verifier)
+	err = token.refresh(&config, &verifier, r)
 	if assert.Error(t, err) {
 		assert.Contains(t, err.Error(), "refresh token not set")
 	}
 	token.RefreshToken = xid.New().String()
-	err = token.refresh(&config, &verifier)
+	err = token.refresh(&config, &verifier, r)
 	assert.ErrorIs(t, err, common.ErrGenericFailure)
 
 	newToken := &oauth2.Token{
@@ -564,7 +568,7 @@ func TestOIDCRefreshToken(t *testing.T) {
 	verifier = mockOIDCVerifier{
 		token: &oidc.IDToken{},
 	}
-	err = token.refresh(&config, &verifier)
+	err = token.refresh(&config, &verifier, r)
 	if assert.Error(t, err) {
 		assert.Contains(t, err.Error(), "the refreshed token has no id token")
 	}
@@ -580,7 +584,7 @@ func TestOIDCRefreshToken(t *testing.T) {
 	verifier = mockOIDCVerifier{
 		err: common.ErrGenericFailure,
 	}
-	err = token.refresh(&config, &verifier)
+	err = token.refresh(&config, &verifier, r)
 	assert.ErrorIs(t, err, common.ErrGenericFailure)
 
 	newToken = newToken.WithExtra(map[string]any{
@@ -595,7 +599,7 @@ func TestOIDCRefreshToken(t *testing.T) {
 	verifier = mockOIDCVerifier{
 		token: &oidc.IDToken{},
 	}
-	err = token.refresh(&config, &verifier)
+	err = token.refresh(&config, &verifier, r)
 	if assert.Error(t, err) {
 		assert.Contains(t, err.Error(), "the refreshed token nonce mismatch")
 	}
@@ -604,7 +608,7 @@ func TestOIDCRefreshToken(t *testing.T) {
 			Nonce: token.Nonce,
 		},
 	}
-	err = token.refresh(&config, &verifier)
+	err = token.refresh(&config, &verifier, r)
 	if assert.Error(t, err) {
 		assert.Contains(t, err.Error(), "oidc: claims not set")
 	}
@@ -615,13 +619,114 @@ func TestOIDCRefreshToken(t *testing.T) {
 	verifier = mockOIDCVerifier{
 		token: idToken,
 	}
-	err = token.refresh(&config, &verifier)
+	err = token.refresh(&config, &verifier, r)
 	assert.NoError(t, err)
+	assert.Len(t, token.Permissions, 1)
+	token.Role = nil
+	// user does not exist
+	err = token.refresh(&config, &verifier, r)
+	assert.Error(t, err)
 	require.Len(t, oidcMgr.tokens, 1)
 	oidcMgr.removeToken(token.Cookie)
 	require.Len(t, oidcMgr.tokens, 0)
 }
 
+func TestOIDCRefreshUser(t *testing.T) {
+	token := oidcToken{
+		Cookie:      xid.New().String(),
+		AccessToken: xid.New().String(),
+		TokenType:   "Bearer",
+		ExpiresAt:   util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Minute)),
+		Nonce:       xid.New().String(),
+		Role:        adminRoleFieldValue,
+		Username:    "missing username",
+	}
+	r, err := http.NewRequest(http.MethodGet, webUsersPath, nil)
+	assert.NoError(t, err)
+	err = token.refreshUser(r)
+	assert.Error(t, err)
+	admin := dataprovider.Admin{
+		Username:    "test_oidc_admin_refresh",
+		Password:    "p",
+		Permissions: []string{dataprovider.PermAdminAny},
+		Status:      0,
+		Filters: dataprovider.AdminFilters{
+			Preferences: dataprovider.AdminPreferences{
+				HideUserPageSections: 1 + 2 + 4,
+			},
+		},
+	}
+	err = dataprovider.AddAdmin(&admin, "", "")
+	assert.NoError(t, err)
+
+	token.Username = admin.Username
+	err = token.refreshUser(r)
+	if assert.Error(t, err) {
+		assert.Contains(t, err.Error(), "is disabled")
+	}
+
+	admin.Status = 1
+	err = dataprovider.UpdateAdmin(&admin, "", "")
+	assert.NoError(t, err)
+	err = token.refreshUser(r)
+	assert.NoError(t, err)
+	assert.Equal(t, admin.Permissions, token.Permissions)
+	assert.Equal(t, admin.Filters.Preferences.HideUserPageSections, token.HideUserPageSections)
+
+	err = dataprovider.DeleteAdmin(admin.Username, "", "")
+	assert.NoError(t, err)
+
+	username := "test_oidc_user_refresh_token"
+	user := dataprovider.User{
+		BaseUser: sdk.BaseUser{
+			Username: username,
+			Password: "p",
+			HomeDir:  filepath.Join(os.TempDir(), username),
+			Status:   0,
+			Permissions: map[string][]string{
+				"/": {dataprovider.PermAny},
+			},
+		},
+		Filters: dataprovider.UserFilters{
+			BaseUserFilters: sdk.BaseUserFilters{
+				DeniedProtocols: []string{common.ProtocolHTTP},
+				WebClient:       []string{sdk.WebClientSharesDisabled, sdk.WebClientWriteDisabled},
+			},
+		},
+	}
+	err = dataprovider.AddUser(&user, "", "")
+	assert.NoError(t, err)
+
+	r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
+	assert.NoError(t, err)
+	token.Role = nil
+	token.Username = username
+	assert.False(t, token.isAdmin())
+	err = token.refreshUser(r)
+	if assert.Error(t, err) {
+		assert.Contains(t, err.Error(), "is disabled")
+	}
+	user, err = dataprovider.UserExists(username)
+	assert.NoError(t, err)
+	user.Status = 1
+	err = dataprovider.UpdateUser(&user, "", "")
+	assert.NoError(t, err)
+	err = token.refreshUser(r)
+	if assert.Error(t, err) {
+		assert.Contains(t, err.Error(), "protocol HTTP is not allowed")
+	}
+
+	user.Filters.DeniedProtocols = []string{common.ProtocolFTP}
+	err = dataprovider.UpdateUser(&user, "", "")
+	assert.NoError(t, err)
+	err = token.refreshUser(r)
+	assert.NoError(t, err)
+	assert.Equal(t, user.Filters.WebClient, token.Permissions)
+
+	err = dataprovider.DeleteUser(username, "", "")
+	assert.NoError(t, err)
+}
+
 func TestValidateOIDCToken(t *testing.T) {
 	oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
 	require.True(t, ok)