1 module itsdangerous.timed;
2 
3 import std.stdio;
4 import std.datetime;
5 import std.datetime.systime;
6 import std.algorithm.searching;
7 import std.format;
8 import std.json;
9 
10 import itsdangerous.dsigner;
11 import itsdangerous.encoding;
12 import itsdangerous.serializer;
13 import itsdangerous.exc;
14 
15 class TimestampSigner(DigestMethod, AlgDigestMethod) : Signer!(DigestMethod, AlgDigestMethod) {
16     /+
17     Works like the regular :class:`.Signer` but also records the time
18     of the signing and can be used to expire signatures. The
19     :meth:`unsign` method can raise :exc:`.SignatureExpired` if the
20     unsigning failed because the signature is expired.
21     +/
22     this(string secretKey,
23         string salt = "itsdangerous.Signer",
24         char sep = '.',
25         string keyDerivation = "django-concat"
26             ){
27         super(secretKey, salt, sep, keyDerivation);
28     }
29 
30     final int getTimestamp(){
31         /+Returns the current timestamp. The function must return an
32         integer.
33         +/
34         return cast(int)Clock.currTime.toUnixTime();
35     }
36 
37     final DateTime timestampToDatetime(int ts){
38         /+
39         Used to convert the timestamp from :meth:`get_timestamp` into
40         a datetime object.
41         +/
42         return cast(DateTime)SysTime.fromUnixTime(ts);
43     }
44 
45     override string sign(string value){
46         /+ Signs the given string and also attaches time information. +/
47         string timestamp = base64Encode!(ubyte[])(intToBytes(getTimestamp())); 
48         string _value = value ~ sep ~ timestamp;
49         return _value ~ sep ~ this.Signer.getSignature(_value);
50     }
51 
52     override string unsign(string value, int maxAge = 0, int* tstamp = null){
53         BadSignature sigError;
54         string result;
55         
56         try {
57             result = this.Signer.unsign(value);
58         } catch (BadSignature ex) {
59             sigError = new BadSignature("unsign method (of super class Signer) cannot unsign timed data!");
60             result = "";
61 		}
62         if(!canFind(result, sep)){
63             if (sigError !is null)
64                 throw sigError;
65             auto bts = new BadTimeSignature("timestamp missing");
66             bts.payload = result;
67             throw bts;
68         }
69         import std.array;
70         auto arr = result.rsplit(sep);
71         string _value = arr[0];
72         string timestamp = arr[1];
73         
74         int timestampInt;
75         try{
76             timestampInt = bytesToInt(cast(ubyte[])base64Decode(timestamp));
77         } catch (Exception ex) {
78             throw new Exception("cannot convert bytes to int");
79         }
80 
81         if (sigError !is null){
82             auto bts = new BadTimeSignature(sigError.msg);
83             bts.payload = _value;
84             bts.dateSignedStr = timestamp;
85             throw bts;
86         }
87             
88         if(timestamp is null){
89             auto bts = new BadTimeSignature("Malformed timestamp");
90             bts.payload = _value;
91             throw bts;
92         }
93 
94         if (maxAge != 0){
95             int age = getTimestamp() - timestampInt;
96             if (age > maxAge){
97                 auto sigExpired = new SignatureExpired(format("Signature age %s > %s seconds", age, maxAge));
98                 sigExpired.payload = value;
99                 sigExpired.dateSignedStr = timestampToDatetime(timestampInt).toSimpleString;
100                 throw sigExpired;
101             }
102         }
103 
104         if(tstamp !is null)
105             *tstamp = timestampInt;
106         
107         return _value;
108     }
109 
110     final bool validate(string signedValue, int maxAge){
111         /+ Only validates the given signed value. Returns ``True`` if
112         the signature exists and is valid.+/
113         try{
114             unsign(signedValue, maxAge);
115             return true;
116         } catch (BadSignature ex) {
117             return false;
118         }
119     }
120 }
121 
122 class TimedSerializer(SignerType) : Serializer!SignerType {
123     /*Uses :class:`TimestampSigner` instead of the default
124     :class:`.Signer`.
125     */
126     
127     this(string secretKey, string salt = "itsdangerous"){
128         super(secretKey, salt);
129     }
130 
131     override JSONValue loads(string s, string salt = null, int maxAge = 0, int *tstamp = null){
132         /*Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the
133         signature validation fails. If a ``max_age`` is provided it will
134         ensure the signature is not older than that time in seconds. In
135         case the signature is outdated, :exc:`.SignatureExpired` is
136         raised. All arguments are forwarded to the signer's
137         :meth:`~TimestampSigner.unsign` method.
138         */
139         Exception lastException = null;
140         int timestamp;
141         string base64d;
142         if(signer is null)
143             signer = makeSigner(this.salt);
144         try{
145             base64d = signer.unsign(s, maxAge, &timestamp);
146             auto payload = loadPayload(base64d); 
147             if (tstamp !is null){
148                 *tstamp = timestamp;
149             }
150             return payload;
151 
152         } catch (SignatureExpired ex){
153             throw new SignatureExpired(ex.msg);
154         } catch (BadTimeSignature ex3){
155             lastException = new BadTimeSignature(ex3.msg);
156         } catch (BadSignature ex2){
157             lastException = new BadSignature(ex2.msg);
158         }
159         throw lastException;
160     }
161 
162     /+ ?
163     def loads_unsafe(self, s, max_age=None, salt=None):
164         load_kwargs = {"max_age": max_age}
165         load_payload_kwargs = {}
166         return self._loads_unsafe_impl(s, salt, load_kwargs, load_payload_kwargs)
167     +/
168 }