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 }