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  }