github.com/cs3org/reva/v2@v2.27.7/pkg/ocm/invite/repository/json/json.go (about)

     1  // Copyright 2018-2023 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  	"net/url"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    33  	invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
    34  	"github.com/cs3org/reva/v2/pkg/appctx"
    35  	"github.com/cs3org/reva/v2/pkg/errtypes"
    36  	"github.com/cs3org/reva/v2/pkg/ocm/invite"
    37  	"github.com/cs3org/reva/v2/pkg/ocm/invite/repository/registry"
    38  	"github.com/cs3org/reva/v2/pkg/utils"
    39  	"github.com/cs3org/reva/v2/pkg/utils/cfg"
    40  	"github.com/cs3org/reva/v2/pkg/utils/list"
    41  	"github.com/pkg/errors"
    42  )
    43  
    44  type inviteModel struct {
    45  	File          string
    46  	Invites       map[string]*invitepb.InviteToken `json:"invites"`
    47  	AcceptedUsers map[string][]*userpb.User        `json:"accepted_users"`
    48  }
    49  
    50  type manager struct {
    51  	config       *config
    52  	sync.RWMutex // concurrent access to the file
    53  	model        *inviteModel
    54  }
    55  
    56  type config struct {
    57  	File string `mapstructure:"file"`
    58  }
    59  
    60  func init() {
    61  	registry.Register("json", New)
    62  }
    63  
    64  func (c *config) ApplyDefaults() {
    65  	if c.File == "" {
    66  		c.File = "/var/tmp/reva/ocm-invites.json"
    67  	}
    68  }
    69  
    70  // New returns a new invite manager object.
    71  func New(m map[string]interface{}) (invite.Repository, error) {
    72  	var c config
    73  	if err := cfg.Decode(m, &c); err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	// load or create file
    78  	model, err := loadOrCreate(c.File)
    79  	if err != nil {
    80  		return nil, errors.Wrap(err, "error loading the file containing the invites")
    81  	}
    82  
    83  	manager := &manager{
    84  		config: &c,
    85  		model:  model,
    86  	}
    87  
    88  	return manager, nil
    89  }
    90  
    91  func loadOrCreate(file string) (*inviteModel, error) {
    92  	_, err := os.Stat(file)
    93  	if os.IsNotExist(err) {
    94  		if err := os.MkdirAll(filepath.Dir(file), 0700); err != nil {
    95  			return nil, errors.Wrap(err, "error creating the ocm storage dir: "+filepath.Dir(file))
    96  		}
    97  		if err := os.WriteFile(file, []byte("{}"), 0700); err != nil {
    98  			return nil, errors.Wrap(err, "error creating the invite storage file: "+file)
    99  		}
   100  	}
   101  
   102  	fd, err := os.OpenFile(file, os.O_CREATE, 0644)
   103  	if err != nil {
   104  		return nil, errors.Wrap(err, "error opening the invite storage file: "+file)
   105  	}
   106  	defer fd.Close()
   107  
   108  	data, err := io.ReadAll(fd)
   109  	if err != nil {
   110  		return nil, errors.Wrap(err, "error reading the data")
   111  	}
   112  
   113  	model := &inviteModel{}
   114  	if err := json.Unmarshal(data, model); err != nil {
   115  		return nil, errors.Wrap(err, "error decoding invite data to json")
   116  	}
   117  
   118  	if model.Invites == nil {
   119  		model.Invites = make(map[string]*invitepb.InviteToken)
   120  	}
   121  	if model.AcceptedUsers == nil {
   122  		model.AcceptedUsers = make(map[string][]*userpb.User)
   123  	}
   124  
   125  	model.File = file
   126  	return model, nil
   127  }
   128  
   129  func (model *inviteModel) save() error {
   130  	data, err := json.Marshal(model)
   131  	if err != nil {
   132  		return errors.Wrap(err, "error encoding invite data to json")
   133  	}
   134  
   135  	if err := os.WriteFile(model.File, data, 0644); err != nil {
   136  		return errors.Wrap(err, "error writing invite data to file: "+model.File)
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  func (m *manager) AddToken(ctx context.Context, token *invitepb.InviteToken) error {
   143  	m.Lock()
   144  	defer m.Unlock()
   145  
   146  	m.model.Invites[token.GetToken()] = token
   147  	if err := m.model.save(); err != nil {
   148  		return errors.Wrap(err, "json: error saving model")
   149  	}
   150  	return nil
   151  }
   152  
   153  func (m *manager) GetToken(ctx context.Context, token string) (*invitepb.InviteToken, error) {
   154  	m.RLock()
   155  	defer m.RUnlock()
   156  
   157  	if tkn, ok := m.model.Invites[token]; ok {
   158  		return tkn, nil
   159  	}
   160  	return nil, invite.ErrTokenNotFound
   161  }
   162  
   163  func (m *manager) ListTokens(ctx context.Context, initiator *userpb.UserId) ([]*invitepb.InviteToken, error) {
   164  	m.RLock()
   165  	defer m.RUnlock()
   166  
   167  	tokens := []*invitepb.InviteToken{}
   168  	for _, token := range m.model.Invites {
   169  		if utils.UserEqual(token.UserId, initiator) && !tokenIsExpired(token) {
   170  			tokens = append(tokens, token)
   171  		}
   172  	}
   173  	return tokens, nil
   174  }
   175  
   176  func tokenIsExpired(token *invitepb.InviteToken) bool {
   177  	return token.Expiration != nil && token.Expiration.Seconds < uint64(time.Now().Unix())
   178  }
   179  
   180  func (m *manager) AddRemoteUser(ctx context.Context, initiator *userpb.UserId, remoteUser *userpb.User) error {
   181  	m.Lock()
   182  	defer m.Unlock()
   183  
   184  	for _, acceptedUser := range m.model.AcceptedUsers[initiator.GetOpaqueId()] {
   185  		if acceptedUser.Id.GetOpaqueId() == remoteUser.Id.OpaqueId && idpsEqual(acceptedUser.Id.GetIdp(), remoteUser.Id.Idp) {
   186  			return invite.ErrUserAlreadyAccepted
   187  		}
   188  	}
   189  
   190  	m.model.AcceptedUsers[initiator.GetOpaqueId()] = append(m.model.AcceptedUsers[initiator.GetOpaqueId()], remoteUser)
   191  	if err := m.model.save(); err != nil {
   192  		return errors.Wrap(err, "json: error saving model")
   193  	}
   194  	return nil
   195  }
   196  
   197  func idpsEqual(idp1, idp2 string) bool {
   198  	normalizeIDP := func(s string) (string, error) {
   199  		u, err := url.Parse(s)
   200  		if err != nil {
   201  			return "", errors.New("could not parse url")
   202  		}
   203  
   204  		if u.Scheme == "" {
   205  			return strings.ToLower(u.Path), nil // the string is just a hostname
   206  		}
   207  		return strings.ToLower(u.Hostname()), nil
   208  	}
   209  
   210  	domain1, err := normalizeIDP(idp1)
   211  	if err != nil {
   212  		return false
   213  	}
   214  	domain2, err := normalizeIDP(idp2)
   215  	if err != nil {
   216  		return false
   217  	}
   218  
   219  	return domain1 == domain2
   220  }
   221  
   222  func (m *manager) GetRemoteUser(ctx context.Context, initiator *userpb.UserId, remoteUserID *userpb.UserId) (*userpb.User, error) {
   223  	m.RLock()
   224  	defer m.RUnlock()
   225  
   226  	log := appctx.GetLogger(ctx)
   227  	for _, acceptedUser := range m.model.AcceptedUsers[initiator.GetOpaqueId()] {
   228  		log.Info().Msgf("looking for '%s' at '%s' - considering '%s' at '%s'",
   229  			remoteUserID.OpaqueId,
   230  			remoteUserID.Idp,
   231  			acceptedUser.Id.GetOpaqueId(),
   232  			acceptedUser.Id.GetIdp(),
   233  		)
   234  		if (acceptedUser.Id.GetOpaqueId() == remoteUserID.OpaqueId) && (remoteUserID.Idp == "" || idpsEqual(acceptedUser.Id.GetIdp(), remoteUserID.Idp)) {
   235  			return acceptedUser, nil
   236  		}
   237  	}
   238  	return nil, errtypes.NotFound(remoteUserID.OpaqueId)
   239  }
   240  
   241  func (m *manager) FindRemoteUsers(ctx context.Context, initiator *userpb.UserId, query string) ([]*userpb.User, error) {
   242  	m.RLock()
   243  	defer m.RUnlock()
   244  
   245  	users := []*userpb.User{}
   246  	for _, acceptedUser := range m.model.AcceptedUsers[initiator.GetOpaqueId()] {
   247  		if query == "" || userContains(acceptedUser, query) {
   248  			users = append(users, acceptedUser)
   249  		}
   250  	}
   251  	return users, nil
   252  }
   253  
   254  func userContains(u *userpb.User, query string) bool {
   255  	query = strings.ToLower(query)
   256  	return strings.Contains(strings.ToLower(u.Username), query) || strings.Contains(strings.ToLower(u.DisplayName), query) ||
   257  		strings.Contains(strings.ToLower(u.Mail), query) || strings.Contains(strings.ToLower(u.Id.OpaqueId), query)
   258  }
   259  
   260  func (m *manager) DeleteRemoteUser(ctx context.Context, initiator *userpb.UserId, remoteUser *userpb.UserId) error {
   261  	m.Lock()
   262  	defer m.Unlock()
   263  
   264  	acceptedUsers, ok := m.model.AcceptedUsers[initiator.GetOpaqueId()]
   265  	if !ok {
   266  		return nil
   267  	}
   268  
   269  	for i, user := range acceptedUsers {
   270  		if (user.Id.GetOpaqueId() == remoteUser.OpaqueId) && (remoteUser.Idp == "" || user.Id.GetIdp() == remoteUser.Idp) {
   271  			acceptedUsers = list.Remove(acceptedUsers, i)
   272  			m.model.AcceptedUsers[initiator.GetOpaqueId()] = acceptedUsers
   273  			_ = m.model.save()
   274  			return nil
   275  		}
   276  	}
   277  	return nil
   278  }