github.com/cs3org/reva/v2@v2.27.7/pkg/auth/manager/owncloudsql/accounts/accounts.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 accounts 20 21 import ( 22 "context" 23 "database/sql" 24 "strings" 25 "time" 26 27 "github.com/cs3org/reva/v2/pkg/appctx" 28 "github.com/pkg/errors" 29 ) 30 31 // Accounts represents oc10-style Accounts 32 type Accounts struct { 33 driver string 34 db *sql.DB 35 joinUsername, joinUUID, enableMedialSearch bool 36 selectSQL string 37 } 38 39 // NewMysql returns a new accounts instance connecting to a MySQL database 40 func NewMysql(dsn string, joinUsername, joinUUID, enableMedialSearch bool) (*Accounts, error) { 41 sqldb, err := sql.Open("mysql", dsn) 42 if err != nil { 43 return nil, errors.Wrap(err, "error connecting to the database") 44 } 45 46 // FIXME make configurable 47 sqldb.SetConnMaxLifetime(time.Minute * 3) 48 sqldb.SetConnMaxIdleTime(time.Second * 30) 49 sqldb.SetMaxOpenConns(100) 50 sqldb.SetMaxIdleConns(10) 51 52 err = sqldb.Ping() 53 if err != nil { 54 return nil, errors.Wrap(err, "error connecting to the database") 55 } 56 57 return New("mysql", sqldb, joinUsername, joinUUID, enableMedialSearch) 58 } 59 60 // New returns a new accounts instance connecting to the given sql.DB 61 func New(driver string, sqldb *sql.DB, joinUsername, joinUUID, enableMedialSearch bool) (*Accounts, error) { 62 63 sel := "SELECT id, email, user_id, display_name, quota, last_login, backend, home, state, password" 64 from := ` 65 FROM oc_accounts a 66 LEFT JOIN oc_users u 67 ON a.user_id=u.uid 68 ` 69 if joinUsername { 70 sel += ", p.configvalue AS username" 71 from += `LEFT JOIN oc_preferences p 72 ON a.user_id=p.userid 73 AND p.appid='core' 74 AND p.configkey='username'` 75 } else { 76 // fallback to user_id as username 77 sel += ", user_id AS username" 78 } 79 if joinUUID { 80 sel += ", p2.configvalue AS ownclouduuid" 81 from += `LEFT JOIN oc_preferences p2 82 ON a.user_id=p2.userid 83 AND p2.appid='core' 84 AND p2.configkey='ownclouduuid'` 85 } else { 86 // fallback to user_id as ownclouduuid 87 sel += ", user_id AS ownclouduuid" 88 } 89 90 return &Accounts{ 91 driver: driver, 92 db: sqldb, 93 joinUsername: joinUsername, 94 joinUUID: joinUUID, 95 enableMedialSearch: enableMedialSearch, 96 selectSQL: sel + from, 97 }, nil 98 } 99 100 // Account stores information about accounts. 101 type Account struct { 102 ID uint64 103 Email sql.NullString 104 UserID string 105 DisplayName sql.NullString 106 Quota sql.NullString 107 LastLogin int 108 Backend string 109 Home string 110 State int8 111 PasswordHash string // from oc_users 112 Username sql.NullString // optional comes from the oc_preferences 113 OwnCloudUUID sql.NullString // optional comes from the oc_preferences 114 } 115 116 func (as *Accounts) rowToAccount(ctx context.Context, row Scannable) (*Account, error) { 117 a := Account{} 118 if err := row.Scan(&a.ID, &a.Email, &a.UserID, &a.DisplayName, &a.Quota, &a.LastLogin, &a.Backend, &a.Home, &a.State, &a.PasswordHash, &a.Username, &a.OwnCloudUUID); err != nil { 119 appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping") 120 return nil, err 121 } 122 123 return &a, nil 124 } 125 126 // Scannable describes the interface providing a Scan method 127 type Scannable interface { 128 Scan(...interface{}) error 129 } 130 131 // GetAccountByLogin fetches an account by mail or username 132 func (as *Accounts) GetAccountByLogin(ctx context.Context, login string) (*Account, error) { 133 var row *sql.Row 134 username := strings.ToLower(login) // usernames are lowercased in owncloud classic 135 if as.joinUsername { 136 row = as.db.QueryRowContext(ctx, as.selectSQL+" WHERE a.email=? OR a.lower_user_id=? OR p.configvalue=?", login, username, login) 137 } else { 138 row = as.db.QueryRowContext(ctx, as.selectSQL+" WHERE a.email=? OR a.lower_user_id=?", login, username) 139 } 140 141 return as.rowToAccount(ctx, row) 142 } 143 144 // GetAccountGroups reads the groups for an account 145 func (as *Accounts) GetAccountGroups(ctx context.Context, uid string) ([]string, error) { 146 rows, err := as.db.QueryContext(ctx, "SELECT gid FROM oc_group_user WHERE uid=?", uid) 147 if err != nil { 148 return nil, err 149 } 150 defer rows.Close() 151 152 var group string 153 groups := []string{} 154 for rows.Next() { 155 if err := rows.Scan(&group); err != nil { 156 appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping") 157 continue 158 } 159 groups = append(groups, group) 160 } 161 if err = rows.Err(); err != nil { 162 return nil, err 163 } 164 return groups, nil 165 }