Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / session / session.go
1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
4
5 package session
6
7 import (
8         "encoding/hex"
9         "encoding/json"
10         "errors"
11         "github.com/twinj/uuid"
12         "reflect"
13         "strconv"
14         "strings"
15         "time"
16 )
17
18 const (
19         // The key for the identity of the session
20         SessionIDKey = "_ID"
21         // The expiration date of the session
22         TimestampKey = "_TS"
23         // The value name indicating how long the session should persist - ie should it persist after the browser closes
24         // this is set under the TimestampKey if the session data should expire immediately
25         SessionValueName = "session"
26         // The key container for the json objects of the data, any non strings found in the map will be placed in here
27         // serialized by key using JSON
28         SessionObjectKeyName = "_object_"
29         // The mapped session object
30         SessionMapKeyName = "_map_"
31         // The suffix of the session cookie
32         SessionCookieSuffix = "_SESSION"
33 )
34
35 // Session data, can be any data, there are reserved keywords used by the storage data
36 // SessionIDKey Is the key name for the session
37 // TimestampKey Is the time that the session should expire
38 //
39 type Session map[string]interface{}
40
41 func NewSession() Session {
42         return Session{}
43 }
44
45 // ID retrieves from the cookie or creates a time-based UUID identifying this
46 // session.
47 func (s Session) ID() string {
48         if sessionIDStr, ok := s[SessionIDKey]; ok {
49                 return sessionIDStr.(string)
50         }
51
52         buffer := uuid.NewV4()
53
54         s[SessionIDKey] = hex.EncodeToString(buffer.Bytes())
55         return s[SessionIDKey].(string)
56 }
57
58 // getExpiration return a time.Time with the session's expiration date.
59 // It uses the passed in expireAfterDuration to add with the current time if the timeout is not
60 // browser dependent (ie session). If previous session has set to "session", the time returned is time.IsZero()
61 func (s Session) GetExpiration(expireAfterDuration time.Duration) time.Time {
62         if expireAfterDuration == 0 || s[TimestampKey] == SessionValueName {
63                 // Expire after closing browser
64                 return time.Time{}
65         }
66         return time.Now().Add(expireAfterDuration)
67 }
68
69 // SetNoExpiration sets session to expire when browser session ends
70 func (s Session) SetNoExpiration() {
71         s[TimestampKey] = SessionValueName
72 }
73
74 // SetDefaultExpiration sets session to expire after default duration
75 func (s Session) SetDefaultExpiration() {
76         delete(s, TimestampKey)
77 }
78
79 // sessionTimeoutExpiredOrMissing returns a boolean of whether the session
80 // cookie is either not present or present but beyond its time to live; i.e.,
81 // whether there is not a valid session.
82 func (s Session) SessionTimeoutExpiredOrMissing() bool {
83         if exp, present := s[TimestampKey]; !present {
84                 return true
85         } else if exp == SessionValueName {
86                 return false
87         } else if expInt, _ := strconv.Atoi(exp.(string)); int64(expInt) < time.Now().Unix() {
88                 return true
89         }
90         return false
91 }
92
93 // Constant error if session value is not found
94 var SESSION_VALUE_NOT_FOUND = errors.New("Session value not found")
95
96 // Get an object or property from the session
97 // it may be embedded inside the session.
98 func (s Session) Get(key string) (newValue interface{}, err error) {
99         // First check to see if it is in the session
100         if v, found := s[key]; found {
101                 return v, nil
102         }
103         return s.GetInto(key, nil, false)
104 }
105
106 // Get into the specified value.
107 // If value exists in the session it will just return the value
108 func (s Session) GetInto(key string, target interface{}, force bool) (result interface{}, err error) {
109         if v, found := s[key]; found && !force {
110                 return v, nil
111         }
112         splitKey := strings.Split(key, ".")
113         rootKey := splitKey[0]
114
115         // Force always recreates the object from the session data map
116         if force {
117                 if target == nil {
118                         if result, err = s.sessionDataFromMap(key); err != nil {
119                                 return
120                         }
121                 } else if result, err = s.sessionDataFromObject(rootKey, target); err != nil {
122                         return
123                 }
124
125                 return s.getNestedProperty(splitKey, result)
126         }
127
128         // Attempt to find the key in the session, this is the most generalized form
129         v, found := s[rootKey]
130         if !found {
131                 if target == nil {
132                         // Try to fetch it from the session
133
134                         if v, err = s.sessionDataFromMap(rootKey); err != nil {
135                                 return
136                         }
137                 } else if v, err = s.sessionDataFromObject(rootKey, target); err != nil {
138                         return
139                 }
140         }
141
142         return s.getNestedProperty(splitKey, v)
143 }
144
145 // Returns the default value if the key is not found
146 func (s Session) GetDefault(key string, value interface{}, defaultValue interface{}) interface{} {
147         v, e := s.GetInto(key, value, false)
148         if e != nil {
149                 v = defaultValue
150         }
151         return v
152 }
153
154 // Extract the values from the session
155 func (s Session) GetProperty(key string, value interface{}) (interface{}, error) {
156         // Capitalize the first letter
157         key = strings.Title(key)
158
159         sessionLog.Info("getProperty", "key", key, "value", value)
160
161         // For a map it is easy
162         if reflect.TypeOf(value).Kind() == reflect.Map {
163                 val := reflect.ValueOf(value)
164                 valueOf := val.MapIndex(reflect.ValueOf(key))
165                 if valueOf == reflect.Zero(reflect.ValueOf(value).Type()) {
166                         return nil, nil
167                 }
168                 //idx := val.MapIndex(reflect.ValueOf(key))
169                 if !valueOf.IsValid() {
170                         return nil, nil
171                 }
172
173                 return valueOf.Interface(), nil
174         }
175
176         objValue := s.reflectValue(value)
177         field := objValue.FieldByName(key)
178         if !field.IsValid() {
179                 return nil, SESSION_VALUE_NOT_FOUND
180         }
181
182         return field.Interface(), nil
183 }
184
185 // Places the object into the session, a nil value will cause remove the key from the session
186 // (or you can use the Session.Del(key) function
187 func (s Session) Set(key string, value interface{}) error {
188         if value == nil {
189                 s.Del(key)
190                 return nil
191         }
192
193         s[key] = value
194         return nil
195 }
196
197 // Delete the key from the sessionObjects and Session
198 func (s Session) Del(key string) {
199         sessionJsonMap := s.getSessionJsonMap()
200         delete(sessionJsonMap, key)
201         delete(s, key)
202 }
203
204 // Extracts the session as a map of [string keys] and json values
205 func (s Session) getSessionJsonMap() map[string]string {
206         if sessionJson, found := s[SessionObjectKeyName]; found {
207                 if _, valid := sessionJson.(map[string]string); !valid {
208                         sessionLog.Error("Session object key corrupted, reset", "was", sessionJson)
209                         s[SessionObjectKeyName] = map[string]string{}
210                 }
211                 // serialized data inside the session _objects
212         } else {
213                 s[SessionObjectKeyName] = map[string]string{}
214         }
215
216         return s[SessionObjectKeyName].(map[string]string)
217 }
218
219 // Convert the map to a simple map[string]string map
220 // this will marshal any non string objects encountered and store them the the jsonMap
221 // The expiration time will also be assigned
222 func (s Session) Serialize() map[string]string {
223         sessionJsonMap := s.getSessionJsonMap()
224         newMap := map[string]string{}
225         newObjectMap := map[string]string{}
226         for key, value := range sessionJsonMap {
227                 newObjectMap[key] = value
228         }
229         for key, value := range s {
230                 if key == SessionObjectKeyName || key == SessionMapKeyName {
231                         continue
232                 }
233                 if reflect.ValueOf(value).Kind() == reflect.String {
234                         newMap[key] = value.(string)
235                         continue
236                 }
237                 println("Serialize the data for", key)
238                 if data, err := json.Marshal(value); err != nil {
239                         sessionLog.Error("Unable to marshal session ", "key", key, "error", err)
240                         continue
241                 } else {
242                         newObjectMap[key] = string(data)
243                 }
244         }
245         if len(newObjectMap) > 0 {
246                 if data, err := json.Marshal(newObjectMap); err != nil {
247                         sessionLog.Error("Unable to marshal session ", "key", SessionObjectKeyName, "error", err)
248
249                 } else {
250                         newMap[SessionObjectKeyName] = string(data)
251                 }
252         }
253
254         return newMap
255 }
256
257 // Set the session object from the loaded data
258 func (s Session) Load(data map[string]string) {
259         for key, value := range data {
260                 if key == SessionObjectKeyName {
261                         target := map[string]string{}
262                         if err := json.Unmarshal([]byte(value), &target); err != nil {
263                                 sessionLog.Error("Unable to unmarshal session ", "key", SessionObjectKeyName, "error", err)
264                         } else {
265                                 s[key] = target
266                         }
267                 } else {
268                         s[key] = value
269                 }
270
271         }
272 }
273
274 // Checks to see if the session is empty
275 func (s Session) Empty() bool {
276         i := 0
277         for k := range s {
278                 i++
279                 if k == SessionObjectKeyName || k == SessionMapKeyName {
280                         continue
281                 }
282         }
283         return i == 0
284 }
285
286 func (s *Session) reflectValue(obj interface{}) reflect.Value {
287         var val reflect.Value
288
289         if reflect.TypeOf(obj).Kind() == reflect.Ptr {
290                 val = reflect.ValueOf(obj).Elem()
291         } else {
292                 val = reflect.ValueOf(obj)
293         }
294
295         return val
296 }
297
298 // Starting at position 1 drill into the object
299 func (s Session) getNestedProperty(keys []string, newValue interface{}) (result interface{}, err error) {
300         for x := 1; x < len(keys); x++ {
301                 newValue, err = s.GetProperty(keys[x], newValue)
302                 if err != nil || newValue == nil {
303                         return newValue, err
304                 }
305         }
306         return newValue, nil
307 }
308
309 // Always converts the data from the session mapped objects into the target,
310 // it will store the results under the session key name SessionMapKeyName
311 func (s Session) sessionDataFromMap(key string) (result interface{}, err error) {
312         var mapValue map[string]interface{}
313         uncastMapValue, found := s[SessionMapKeyName]
314         if !found {
315                 mapValue = map[string]interface{}{}
316                 s[SessionMapKeyName] = mapValue
317         } else if mapValue, found = uncastMapValue.(map[string]interface{}); !found {
318                 // Unusual means that the value in the session was not expected
319                 sessionLog.Errorf("Unusual means that the value in the session was not expected", "session", uncastMapValue)
320                 mapValue = map[string]interface{}{}
321                 s[SessionMapKeyName] = mapValue
322         }
323
324         // Try to extract the key from the map
325         result, found = mapValue[key]
326         if !found {
327                 result, err = s.convertSessionData(key, nil)
328                 if err == nil {
329                         mapValue[key] = result
330                 }
331         }
332         return
333 }
334
335 // Unpack the object from the session map and store it in the session when done, if no error occurs
336 func (s Session) sessionDataFromObject(key string, newValue interface{}) (result interface{}, err error) {
337         result, err = s.convertSessionData(key, newValue)
338         if err != nil {
339                 return
340         }
341         s[key] = result
342         return
343 }
344
345 // Converts from the session json map into the target,
346 func (s Session) convertSessionData(key string, target interface{}) (result interface{}, err error) {
347         sessionJsonMap := s.getSessionJsonMap()
348         v, found := sessionJsonMap[key]
349         if !found {
350                 return target, SESSION_VALUE_NOT_FOUND
351         }
352
353         // Create a target if needed
354         if target == nil {
355                 target = map[string]interface{}{}
356                 if err := json.Unmarshal([]byte(v), &target); err != nil {
357                         return target, err
358                 }
359         } else if err := json.Unmarshal([]byte(v), target); err != nil {
360                 return target, err
361         }
362         result = target
363         return
364 }