github.com/cs3org/reva/v2@v2.27.7/pkg/auth/manager/owncloudsql/owncloudsql.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package owncloudsql 20 21 import ( 22 "context" 23 "crypto/hmac" 24 "crypto/sha1" 25 "encoding/hex" 26 "fmt" 27 "strings" 28 29 "golang.org/x/crypto/bcrypt" 30 31 authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" 32 user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 33 "github.com/cs3org/reva/v2/pkg/appctx" 34 "github.com/cs3org/reva/v2/pkg/auth" 35 "github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql/accounts" 36 "github.com/cs3org/reva/v2/pkg/auth/manager/registry" 37 "github.com/cs3org/reva/v2/pkg/auth/scope" 38 "github.com/cs3org/reva/v2/pkg/errtypes" 39 "github.com/mitchellh/mapstructure" 40 "github.com/pkg/errors" 41 42 // Provides mysql drivers 43 _ "github.com/go-sql-driver/mysql" 44 ) 45 46 func init() { 47 registry.Register("owncloudsql", NewMysql) 48 } 49 50 type manager struct { 51 c *config 52 db *accounts.Accounts 53 } 54 55 type config struct { 56 DbUsername string `mapstructure:"dbusername"` 57 DbPassword string `mapstructure:"dbpassword"` 58 DbHost string `mapstructure:"dbhost"` 59 DbPort int `mapstructure:"dbport"` 60 DbName string `mapstructure:"dbname"` 61 Idp string `mapstructure:"idp"` 62 Nobody int64 `mapstructure:"nobody"` 63 LegacySalt string `mapstructure:"legacy_salt"` 64 JoinUsername bool `mapstructure:"join_username"` 65 JoinOwnCloudUUID bool `mapstructure:"join_ownclouduuid"` 66 } 67 68 // NewMysql returns a new auth manager connection to an owncloud mysql database 69 func NewMysql(m map[string]interface{}) (auth.Manager, error) { 70 mgr := &manager{} 71 err := mgr.Configure(m) 72 if err != nil { 73 err = errors.Wrap(err, "error creating a new auth manager") 74 return nil, err 75 } 76 77 mgr.db, err = accounts.NewMysql( 78 fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", mgr.c.DbUsername, mgr.c.DbPassword, mgr.c.DbHost, mgr.c.DbPort, mgr.c.DbName), 79 mgr.c.JoinUsername, 80 mgr.c.JoinOwnCloudUUID, 81 false, 82 ) 83 if err != nil { 84 return nil, err 85 } 86 87 return mgr, nil 88 } 89 90 func (m *manager) Configure(ml map[string]interface{}) error { 91 c, err := parseConfig(ml) 92 if err != nil { 93 return err 94 } 95 96 if c.Nobody == 0 { 97 c.Nobody = 99 98 } 99 100 m.c = c 101 return nil 102 } 103 104 func parseConfig(m map[string]interface{}) (*config, error) { 105 c := &config{} 106 if err := mapstructure.Decode(m, &c); err != nil { 107 return nil, err 108 } 109 return c, nil 110 } 111 112 func (m *manager) Authenticate(ctx context.Context, login, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { 113 log := appctx.GetLogger(ctx) 114 115 // 1. find user by login 116 117 account, err := m.db.GetAccountByLogin(ctx, login) 118 if err != nil { 119 return nil, nil, errtypes.NotFound(login) 120 } 121 // 2. verify the user password 122 if !m.verify(clientSecret, account.PasswordHash) { 123 return nil, nil, errtypes.InvalidCredentials(login) 124 } 125 126 userID := &user.UserId{ 127 Idp: m.c.Idp, 128 OpaqueId: account.OwnCloudUUID.String, 129 Type: user.UserType_USER_TYPE_PRIMARY, // TODO: assign the appropriate user type for guest accounts 130 } 131 132 u := &user.User{ 133 Id: userID, 134 // TODO add more claims from the StandardClaims, eg EmailVerified and lastlogin 135 Username: account.Username.String, 136 Mail: account.Email.String, 137 DisplayName: account.DisplayName.String, 138 //UidNumber: uidNumber, 139 //GidNumber: gidNumber, 140 } 141 142 if u.Groups, err = m.db.GetAccountGroups(ctx, account.UserID); err != nil { 143 return nil, nil, err 144 } 145 146 var scopes map[string]*authpb.Scope 147 if userID != nil && (userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT || userID.Type == user.UserType_USER_TYPE_FEDERATED) { 148 scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) 149 if err != nil { 150 return nil, nil, err 151 } 152 } else { 153 scopes, err = scope.AddOwnerScope(nil) 154 if err != nil { 155 return nil, nil, err 156 } 157 } 158 // do not log password hash 159 account.PasswordHash = "***redacted***" 160 log.Debug().Interface("account", account).Interface("user", u).Msg("authenticated user") 161 162 return u, scopes, nil 163 } 164 165 func (m *manager) verify(password, hash string) bool { 166 splitHash := strings.SplitN(hash, "|", 2) 167 switch len(splitHash) { 168 case 2: 169 if splitHash[0] == "1" { 170 return m.verifyHashV1(password, splitHash[1]) 171 } 172 case 1: 173 return m.legacyHashVerify(password, hash) 174 } 175 return false 176 } 177 178 func (m *manager) legacyHashVerify(password, hash string) bool { 179 // TODO rehash $newHash = $this->hash($message); 180 switch len(hash) { 181 case 60: // legacy PHPass hash 182 return nil == bcrypt.CompareHashAndPassword([]byte(hash), []byte(password+m.c.LegacySalt)) 183 case 40: // legacy sha1 hash 184 h := sha1.Sum([]byte(password)) 185 return hmac.Equal([]byte(hash), []byte(hex.EncodeToString(h[:]))) 186 } 187 return false 188 } 189 func (m *manager) verifyHashV1(password, hash string) bool { 190 // TODO implement password_needs_rehash 191 return nil == bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 192 }