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  }