github.com/cs3org/reva/v2@v2.27.7/pkg/cbox/publicshare/sql/sql.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 sql 20 21 import ( 22 "context" 23 "database/sql" 24 "fmt" 25 "os" 26 "os/signal" 27 "strconv" 28 "strings" 29 "syscall" 30 "time" 31 32 "golang.org/x/crypto/bcrypt" 33 34 gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 35 user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 36 link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" 37 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 38 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 39 conversions "github.com/cs3org/reva/v2/pkg/cbox/utils" 40 "github.com/cs3org/reva/v2/pkg/errtypes" 41 "github.com/cs3org/reva/v2/pkg/publicshare" 42 "github.com/cs3org/reva/v2/pkg/publicshare/manager/registry" 43 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 44 "github.com/cs3org/reva/v2/pkg/utils" 45 "github.com/mitchellh/mapstructure" 46 "github.com/pkg/errors" 47 ) 48 49 const publicShareType = 3 50 51 func init() { 52 registry.Register("sql", New) 53 } 54 55 type config struct { 56 SharePasswordHashCost int `mapstructure:"password_hash_cost"` 57 JanitorRunInterval int `mapstructure:"janitor_run_interval"` 58 EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"` 59 DbUsername string `mapstructure:"db_username"` 60 DbPassword string `mapstructure:"db_password"` 61 DbHost string `mapstructure:"db_host"` 62 DbPort int `mapstructure:"db_port"` 63 DbName string `mapstructure:"db_name"` 64 GatewaySvc string `mapstructure:"gatewaysvc"` 65 } 66 67 type manager struct { 68 c *config 69 db *sql.DB 70 client gatewayv1beta1.GatewayAPIClient 71 } 72 73 func (c *config) init() { 74 if c.SharePasswordHashCost == 0 { 75 c.SharePasswordHashCost = 11 76 } 77 if c.JanitorRunInterval == 0 { 78 c.JanitorRunInterval = 3600 79 } 80 } 81 82 func (m *manager) startJanitorRun() { 83 if !m.c.EnableExpiredSharesCleanup { 84 return 85 } 86 87 ticker := time.NewTicker(time.Duration(m.c.JanitorRunInterval) * time.Second) 88 work := make(chan os.Signal, 1) 89 signal.Notify(work, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT) 90 91 for { 92 select { 93 case <-work: 94 return 95 case <-ticker.C: 96 _ = m.cleanupExpiredShares() 97 } 98 } 99 } 100 101 // New returns a new public share manager. 102 func New(m map[string]interface{}) (publicshare.Manager, error) { 103 c := &config{} 104 if err := mapstructure.Decode(m, c); err != nil { 105 return nil, err 106 } 107 c.init() 108 109 db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName)) 110 if err != nil { 111 return nil, err 112 } 113 114 gw, err := pool.GetGatewayServiceClient(c.GatewaySvc) 115 if err != nil { 116 return nil, err 117 } 118 119 mgr := manager{ 120 c: c, 121 db: db, 122 client: gw, 123 } 124 go mgr.startJanitorRun() 125 126 return &mgr, nil 127 } 128 129 func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) { 130 131 tkn := utils.RandString(15) 132 now := time.Now().Unix() 133 134 displayName, ok := rInfo.ArbitraryMetadata.Metadata["name"] 135 if !ok { 136 displayName = tkn 137 } 138 createdAt := &typespb.Timestamp{ 139 Seconds: uint64(now), 140 } 141 142 creator := conversions.FormatUserID(u.Id) 143 owner := conversions.FormatUserID(rInfo.Owner) 144 permissions := conversions.SharePermToInt(g.Permissions.Permissions) 145 itemType := conversions.ResourceTypeToItem(rInfo.Type) 146 prefix := rInfo.Id.SpaceId 147 itemSource := rInfo.Id.OpaqueId 148 fileSource, err := strconv.ParseUint(itemSource, 10, 64) 149 if err != nil { 150 // it can be the case that the item source may be a character string 151 // we leave fileSource blank in that case 152 fileSource = 0 153 } 154 155 query := "insert into oc_share set share_type=?,uid_owner=?,uid_initiator=?,item_type=?,fileid_prefix=?,item_source=?,file_source=?,permissions=?,stime=?,token=?,share_name=?" 156 params := []interface{}{publicShareType, owner, creator, itemType, prefix, itemSource, fileSource, permissions, now, tkn, displayName} 157 158 var passwordProtected bool 159 password := g.Password 160 if password != "" { 161 password, err = hashPassword(password, m.c.SharePasswordHashCost) 162 if err != nil { 163 return nil, errors.Wrap(err, "could not hash share password") 164 } 165 passwordProtected = true 166 167 query += ",share_with=?" 168 params = append(params, password) 169 } 170 171 if g.Expiration != nil && g.Expiration.Seconds != 0 { 172 t := time.Unix(int64(g.Expiration.Seconds), 0) 173 query += ",expiration=?" 174 params = append(params, t) 175 } 176 177 stmt, err := m.db.Prepare(query) 178 if err != nil { 179 return nil, err 180 } 181 result, err := stmt.Exec(params...) 182 if err != nil { 183 return nil, err 184 } 185 lastID, err := result.LastInsertId() 186 if err != nil { 187 return nil, err 188 } 189 190 return &link.PublicShare{ 191 Id: &link.PublicShareId{ 192 OpaqueId: strconv.FormatInt(lastID, 10), 193 }, 194 Owner: rInfo.GetOwner(), 195 Creator: u.Id, 196 ResourceId: rInfo.Id, 197 Token: tkn, 198 Permissions: g.Permissions, 199 Ctime: createdAt, 200 Mtime: createdAt, 201 PasswordProtected: passwordProtected, 202 Expiration: g.Expiration, 203 DisplayName: displayName, 204 }, nil 205 } 206 207 func (m *manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error) { 208 query := "update oc_share set " 209 paramsMap := map[string]interface{}{} 210 params := []interface{}{} 211 212 now := time.Now().Unix() 213 uid := conversions.FormatUserID(u.Id) 214 215 switch req.GetUpdate().GetType() { 216 case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME: 217 paramsMap["share_name"] = req.Update.GetDisplayName() 218 case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS: 219 paramsMap["permissions"] = conversions.SharePermToInt(req.Update.GetGrant().GetPermissions().Permissions) 220 case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION: 221 paramsMap["expiration"] = time.Unix(int64(req.Update.GetGrant().Expiration.Seconds), 0) 222 case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD: 223 if req.Update.GetGrant().Password == "" { 224 paramsMap["share_with"] = "" 225 } else { 226 h, err := hashPassword(req.Update.GetGrant().Password, m.c.SharePasswordHashCost) 227 if err != nil { 228 return nil, errors.Wrap(err, "could not hash share password") 229 } 230 paramsMap["share_with"] = h 231 } 232 default: 233 return nil, fmt.Errorf("invalid update type: %v", req.GetUpdate().GetType()) 234 } 235 236 for k, v := range paramsMap { 237 query += k + "=?" 238 params = append(params, v) 239 } 240 241 switch { 242 case req.Ref.GetId() != nil: 243 query += ",stime=? where id=? AND (uid_owner=? or uid_initiator=?)" 244 params = append(params, now, req.Ref.GetId().OpaqueId, uid, uid) 245 case req.Ref.GetToken() != "": 246 query += ",stime=? where token=? AND (uid_owner=? or uid_initiator=?)" 247 params = append(params, now, req.Ref.GetToken(), uid, uid) 248 default: 249 return nil, errtypes.NotFound(req.Ref.String()) 250 } 251 252 stmt, err := m.db.Prepare(query) 253 if err != nil { 254 return nil, err 255 } 256 if _, err = stmt.Exec(params...); err != nil { 257 return nil, err 258 } 259 260 return m.GetPublicShare(ctx, u, req.Ref, false) 261 } 262 263 func (m *manager) getByToken(ctx context.Context, token string, u *user.User) (*link.PublicShare, string, error) { 264 s := conversions.DBShare{Token: token} 265 query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, id, stime, permissions FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND share_type=? AND token=?" 266 if err := m.db.QueryRow(query, publicShareType, token).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions); err != nil { 267 if err == sql.ErrNoRows { 268 return nil, "", errtypes.NotFound(token) 269 } 270 return nil, "", err 271 } 272 share, err := conversions.ConvertToCS3PublicShare(ctx, m.client, s) 273 if err != nil { 274 return nil, "", err 275 } 276 return share, s.ShareWith, nil 277 } 278 279 func (m *manager) getByID(ctx context.Context, id *link.PublicShareId, u *user.User) (*link.PublicShare, string, error) { 280 uid := conversions.FormatUserID(u.Id) 281 s := conversions.DBShare{ID: id.OpaqueId} 282 query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, coalesce(token,'') as token, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, stime, permissions FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND share_type=? AND id=? AND (uid_owner=? OR uid_initiator=?)" 283 if err := m.db.QueryRow(query, publicShareType, id.OpaqueId, uid, uid).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.Token, &s.Expiration, &s.ShareName, &s.STime, &s.Permissions); err != nil { 284 if err == sql.ErrNoRows { 285 return nil, "", errtypes.NotFound(id.OpaqueId) 286 } 287 return nil, "", err 288 } 289 share, err := conversions.ConvertToCS3PublicShare(ctx, m.client, s) 290 if err != nil { 291 return nil, "", err 292 } 293 return share, s.ShareWith, nil 294 } 295 296 func (m *manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (*link.PublicShare, error) { 297 var s *link.PublicShare 298 var pw string 299 var err error 300 switch { 301 case ref.GetId() != nil: 302 s, pw, err = m.getByID(ctx, ref.GetId(), u) 303 case ref.GetToken() != "": 304 s, pw, err = m.getByToken(ctx, ref.GetToken(), u) 305 default: 306 err = errtypes.NotFound(ref.String()) 307 } 308 if err != nil { 309 return nil, err 310 } 311 312 if expired(s) { 313 if err := m.cleanupExpiredShares(); err != nil { 314 return nil, err 315 } 316 return nil, errtypes.NotFound(ref.String()) 317 } 318 319 if s.PasswordProtected && sign { 320 if err := publicshare.AddSignature(s, pw); err != nil { 321 return nil, err 322 } 323 324 } 325 326 return s, nil 327 } 328 329 func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error) { 330 uid := conversions.FormatUserID(u.Id) 331 query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, coalesce(token,'') as token, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, id, stime, permissions FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?) AND (share_type=?)" 332 var resourceFilters, ownerFilters, creatorFilters string 333 var resourceParams, ownerParams, creatorParams []interface{} 334 params := []interface{}{uid, uid, publicShareType} 335 336 for _, f := range filters { 337 switch f.Type { 338 case link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID: 339 if len(resourceFilters) != 0 { 340 resourceFilters += " OR " 341 } 342 resourceFilters += "(fileid_prefix=? AND item_source=?)" 343 resourceParams = append(resourceParams, f.GetResourceId().SpaceId, f.GetResourceId().OpaqueId) 344 case link.ListPublicSharesRequest_Filter_TYPE_OWNER: 345 if len(ownerFilters) != 0 { 346 ownerFilters += " OR " 347 } 348 ownerFilters += "(uid_owner=?)" 349 ownerParams = append(ownerParams, conversions.FormatUserID(f.GetOwner())) 350 case link.ListPublicSharesRequest_Filter_TYPE_CREATOR: 351 if len(creatorFilters) != 0 { 352 creatorFilters += " OR " 353 } 354 creatorFilters += "(uid_initiator=?)" 355 creatorParams = append(creatorParams, conversions.FormatUserID(f.GetCreator())) 356 } 357 } 358 if resourceFilters != "" { 359 query = fmt.Sprintf("%s AND (%s)", query, resourceFilters) 360 params = append(params, resourceParams...) 361 } 362 if ownerFilters != "" { 363 query = fmt.Sprintf("%s AND (%s)", query, ownerFilters) 364 params = append(params, ownerParams...) 365 } 366 if creatorFilters != "" { 367 query = fmt.Sprintf("%s AND (%s)", query, creatorFilters) 368 params = append(params, creatorParams...) 369 } 370 371 rows, err := m.db.Query(query, params...) 372 if err != nil { 373 return nil, err 374 } 375 defer rows.Close() 376 377 var s conversions.DBShare 378 shares := []*link.PublicShare{} 379 for rows.Next() { 380 if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.Token, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions); err != nil { 381 continue 382 } 383 cs3Share, err := conversions.ConvertToCS3PublicShare(ctx, m.client, s) 384 if err != nil { 385 return nil, err 386 } 387 if expired(cs3Share) { 388 _ = m.cleanupExpiredShares() 389 } else { 390 if cs3Share.PasswordProtected && sign { 391 if err := publicshare.AddSignature(cs3Share, s.ShareWith); err != nil { 392 return nil, err 393 } 394 } 395 shares = append(shares, cs3Share) 396 } 397 } 398 if err = rows.Err(); err != nil { 399 return nil, err 400 } 401 402 return shares, nil 403 } 404 405 func (m *manager) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error { 406 uid := conversions.FormatUserID(u.Id) 407 query := "delete from oc_share where " 408 params := []interface{}{} 409 410 switch { 411 case ref.GetId() != nil && ref.GetId().OpaqueId != "": 412 query += "id=? AND (uid_owner=? or uid_initiator=?)" 413 params = append(params, ref.GetId().OpaqueId, uid, uid) 414 case ref.GetToken() != "": 415 query += "token=? AND (uid_owner=? or uid_initiator=?)" 416 params = append(params, ref.GetToken(), uid, uid) 417 default: 418 return errtypes.NotFound(ref.String()) 419 } 420 421 stmt, err := m.db.Prepare(query) 422 if err != nil { 423 return err 424 } 425 res, err := stmt.Exec(params...) 426 if err != nil { 427 return err 428 } 429 430 rowCnt, err := res.RowsAffected() 431 if err != nil { 432 return err 433 } 434 if rowCnt == 0 { 435 return errtypes.NotFound(ref.String()) 436 } 437 return nil 438 } 439 440 func (m *manager) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) { 441 s := conversions.DBShare{Token: token} 442 query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, coalesce(item_type, '') as item_type, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, id, stime, permissions FROM oc_share WHERE share_type=? AND token=?" 443 if err := m.db.QueryRow(query, publicShareType, token).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ItemType, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions); err != nil { 444 if err == sql.ErrNoRows { 445 return nil, errtypes.NotFound(token) 446 } 447 return nil, err 448 } 449 cs3Share, err := conversions.ConvertToCS3PublicShare(ctx, m.client, s) 450 if err != nil { 451 return nil, err 452 } 453 if s.ShareWith != "" { 454 if !authenticate(cs3Share, s.ShareWith, auth) { 455 // if check := checkPasswordHash(auth.Password, s.ShareWith); !check { 456 return nil, errtypes.InvalidCredentials(token) 457 } 458 459 if sign { 460 if err := publicshare.AddSignature(cs3Share, s.ShareWith); err != nil { 461 return nil, err 462 } 463 } 464 } 465 466 if expired(cs3Share) { 467 if err := m.cleanupExpiredShares(); err != nil { 468 return nil, err 469 } 470 return nil, errtypes.NotFound(token) 471 } 472 473 return cs3Share, nil 474 } 475 476 func (m *manager) cleanupExpiredShares() error { 477 if !m.c.EnableExpiredSharesCleanup { 478 return nil 479 } 480 481 query := "update oc_share set orphan = 1 where expiration IS NOT NULL AND expiration < ?" 482 params := []interface{}{time.Now().Format("2006-01-02 03:04:05")} 483 484 stmt, err := m.db.Prepare(query) 485 if err != nil { 486 return err 487 } 488 if _, err = stmt.Exec(params...); err != nil { 489 return err 490 } 491 return nil 492 } 493 494 func expired(s *link.PublicShare) bool { 495 if s.Expiration != nil { 496 if t := time.Unix(int64(s.Expiration.GetSeconds()), int64(s.Expiration.GetNanos())); t.Before(time.Now()) { 497 return true 498 } 499 } 500 return false 501 } 502 503 func hashPassword(password string, cost int) (string, error) { 504 bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost) 505 return "1|" + string(bytes), err 506 } 507 508 func checkPasswordHash(password, hash string) bool { 509 err := bcrypt.CompareHashAndPassword([]byte(strings.TrimPrefix(hash, "1|")), []byte(password)) 510 return err == nil 511 } 512 513 func authenticate(share *link.PublicShare, pw string, auth *link.PublicShareAuthentication) bool { 514 switch { 515 case auth.GetPassword() != "": 516 return checkPasswordHash(auth.GetPassword(), pw) 517 case auth.GetSignature() != nil: 518 sig := auth.GetSignature() 519 now := time.Now() 520 expiration := time.Unix(int64(sig.GetSignatureExpiration().GetSeconds()), int64(sig.GetSignatureExpiration().GetNanos())) 521 if now.After(expiration) { 522 return false 523 } 524 s, err := publicshare.CreateSignature(share.Token, pw, expiration) 525 if err != nil { 526 // TODO(labkode): pass context to call to log err. 527 // No we are blind 528 return false 529 } 530 return sig.GetSignature() == s 531 } 532 return false 533 }