github.com/cs3org/reva/v2@v2.27.7/pkg/appauth/manager/json/json.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 json
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"io"
    25  	"os"
    26  	"sync"
    27  	"time"
    28  
    29  	apppb "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1"
    30  	authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
    31  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    32  	typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    33  	"github.com/cs3org/reva/v2/pkg/appauth"
    34  	"github.com/cs3org/reva/v2/pkg/appauth/manager/registry"
    35  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    36  	"github.com/cs3org/reva/v2/pkg/errtypes"
    37  	"github.com/mitchellh/mapstructure"
    38  	"github.com/pkg/errors"
    39  	"github.com/sethvargo/go-password/password"
    40  	"golang.org/x/crypto/bcrypt"
    41  	"google.golang.org/protobuf/proto"
    42  )
    43  
    44  func init() {
    45  	registry.Register("json", New)
    46  }
    47  
    48  type config struct {
    49  	File             string `mapstructure:"file"`
    50  	TokenStrength    int    `mapstructure:"token_strength"`
    51  	PasswordHashCost int    `mapstructure:"password_hash_cost"`
    52  }
    53  
    54  type jsonManager struct {
    55  	sync.Mutex
    56  	config *config
    57  	// map[userid][password]AppPassword
    58  	passwords map[string]map[string]*apppb.AppPassword
    59  }
    60  
    61  // New returns a new mgr.
    62  func New(m map[string]interface{}) (appauth.Manager, error) {
    63  	c, err := parseConfig(m)
    64  	if err != nil {
    65  		return nil, errors.Wrap(err, "error creating a new manager")
    66  	}
    67  
    68  	c.init()
    69  
    70  	// load or create file
    71  	manager, err := loadOrCreate(c.File)
    72  	if err != nil {
    73  		return nil, errors.Wrap(err, "error loading the file containing the application passwords")
    74  	}
    75  
    76  	manager.config = c
    77  
    78  	return manager, nil
    79  }
    80  
    81  func (c *config) init() {
    82  	if c.File == "" {
    83  		c.File = "/var/tmp/reva/appauth.json"
    84  	}
    85  	if c.TokenStrength == 0 {
    86  		c.TokenStrength = 16
    87  	}
    88  	if c.PasswordHashCost == 0 {
    89  		c.PasswordHashCost = 11
    90  	}
    91  }
    92  
    93  func parseConfig(m map[string]interface{}) (*config, error) {
    94  	c := &config{}
    95  	if err := mapstructure.Decode(m, c); err != nil {
    96  		return nil, err
    97  	}
    98  	return c, nil
    99  }
   100  
   101  func loadOrCreate(file string) (*jsonManager, error) {
   102  	stat, err := os.Stat(file)
   103  	if os.IsNotExist(err) || stat.Size() == 0 {
   104  		if err = os.WriteFile(file, []byte("{}"), 0644); err != nil {
   105  			return nil, errors.Wrapf(err, "error creating the file %s", file)
   106  		}
   107  	}
   108  
   109  	fd, err := os.OpenFile(file, os.O_RDONLY, 0)
   110  	if err != nil {
   111  		return nil, errors.Wrapf(err, "error opening the file %s", file)
   112  	}
   113  	defer fd.Close()
   114  
   115  	data, err := io.ReadAll(fd)
   116  	if err != nil {
   117  		return nil, errors.Wrapf(err, "error reading the file %s", file)
   118  	}
   119  
   120  	m := &jsonManager{}
   121  	if err = json.Unmarshal(data, &m.passwords); err != nil {
   122  		return nil, errors.Wrapf(err, "error parsing the file %s", file)
   123  	}
   124  
   125  	if m.passwords == nil {
   126  		m.passwords = make(map[string]map[string]*apppb.AppPassword)
   127  	}
   128  
   129  	return m, nil
   130  }
   131  
   132  func (mgr *jsonManager) GenerateAppPassword(ctx context.Context, scope map[string]*authpb.Scope, label string, expiration *typespb.Timestamp) (*apppb.AppPassword, error) {
   133  	token, err := password.Generate(mgr.config.TokenStrength, mgr.config.TokenStrength/2, 0, false, false)
   134  	if err != nil {
   135  		return nil, errors.Wrap(err, "error creating new token")
   136  	}
   137  	tokenHashed, err := bcrypt.GenerateFromPassword([]byte(token), mgr.config.PasswordHashCost)
   138  	if err != nil {
   139  		return nil, errors.Wrap(err, "error creating new token")
   140  	}
   141  	userID := ctxpkg.ContextMustGetUser(ctx).GetId()
   142  	ctime := now()
   143  
   144  	password := string(tokenHashed)
   145  	appPass := &apppb.AppPassword{
   146  		Password:   password,
   147  		TokenScope: scope,
   148  		Label:      label,
   149  		Expiration: expiration,
   150  		Ctime:      ctime,
   151  		Utime:      ctime,
   152  		User:       userID,
   153  	}
   154  	mgr.Lock()
   155  	defer mgr.Unlock()
   156  
   157  	// check if user has some previous password
   158  	if _, ok := mgr.passwords[userID.String()]; !ok {
   159  		mgr.passwords[userID.String()] = make(map[string]*apppb.AppPassword)
   160  	}
   161  
   162  	mgr.passwords[userID.String()][password] = appPass
   163  
   164  	err = mgr.save()
   165  	if err != nil {
   166  		return nil, errors.Wrap(err, "error saving new token")
   167  	}
   168  
   169  	clonedAppPass := proto.Clone(appPass).(*apppb.AppPassword)
   170  	clonedAppPass.Password = token
   171  	return clonedAppPass, nil
   172  }
   173  
   174  func (mgr *jsonManager) ListAppPasswords(ctx context.Context) ([]*apppb.AppPassword, error) {
   175  	userID := ctxpkg.ContextMustGetUser(ctx).GetId()
   176  	mgr.Lock()
   177  	defer mgr.Unlock()
   178  	appPasswords := []*apppb.AppPassword{}
   179  	for _, pw := range mgr.passwords[userID.String()] {
   180  		appPasswords = append(appPasswords, pw)
   181  	}
   182  	return appPasswords, nil
   183  }
   184  
   185  func (mgr *jsonManager) InvalidateAppPassword(ctx context.Context, password string) error {
   186  	userID := ctxpkg.ContextMustGetUser(ctx).GetId()
   187  	mgr.Lock()
   188  	defer mgr.Unlock()
   189  
   190  	// see if user has a list of passwords
   191  	appPasswords, ok := mgr.passwords[userID.String()]
   192  	if !ok || len(appPasswords) == 0 {
   193  		return errtypes.NotFound("password not found")
   194  	}
   195  
   196  	if _, ok := appPasswords[password]; !ok {
   197  		return errtypes.NotFound("password not found")
   198  	}
   199  	delete(mgr.passwords[userID.String()], password)
   200  
   201  	// if user has 0 passwords, delete user key from state map
   202  	if len(mgr.passwords[userID.String()]) == 0 {
   203  		delete(mgr.passwords, userID.String())
   204  	}
   205  
   206  	return mgr.save()
   207  }
   208  
   209  func (mgr *jsonManager) GetAppPassword(ctx context.Context, userID *userpb.UserId, password string) (*apppb.AppPassword, error) {
   210  	mgr.Lock()
   211  	defer mgr.Unlock()
   212  
   213  	appPassword, ok := mgr.passwords[userID.String()]
   214  	if !ok {
   215  		return nil, errtypes.NotFound("password not found")
   216  	}
   217  
   218  	for hash, pw := range appPassword {
   219  		err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
   220  		if err == nil {
   221  			// password found
   222  			if pw.Expiration != nil && pw.Expiration.Seconds != 0 && uint64(time.Now().Unix()) > pw.Expiration.Seconds {
   223  				// password expired
   224  				return nil, errtypes.NotFound("password not found")
   225  			}
   226  			// password not expired
   227  			// update last used time
   228  			pw.Utime = now()
   229  			if err := mgr.save(); err != nil {
   230  				return nil, errors.Wrap(err, "error saving file")
   231  			}
   232  
   233  			return pw, nil
   234  		}
   235  	}
   236  
   237  	return nil, errtypes.NotFound("password not found")
   238  }
   239  
   240  func now() *typespb.Timestamp {
   241  	return &typespb.Timestamp{Seconds: uint64(time.Now().Unix())}
   242  }
   243  
   244  func (mgr *jsonManager) save() error {
   245  	data, err := json.Marshal(mgr.passwords)
   246  	if err != nil {
   247  		return errors.Wrap(err, "error encoding json file")
   248  	}
   249  
   250  	if err = os.WriteFile(mgr.config.File, data, 0644); err != nil {
   251  		return errors.Wrapf(err, "error writing to file %s", mgr.config.File)
   252  	}
   253  
   254  	return nil
   255  }