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 }