Passed
Branch development (e0e718)
by Nils
04:45
created

includes/libraries/csrfp/js/csrfprotector.js   B

Complexity

Total Complexity 51
Complexity/F 3.19

Size

Lines of Code 322
Function Count 16

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 1
dl 0
loc 322
rs 8.3206
c 0
b 0
f 0
wmc 51
mnd 4
bc 51
fnc 16
bpm 3.1875
cpm 3.1875
noi 7

How to fix   Complexity   

Complexity

Complex classes like includes/libraries/csrfp/js/csrfprotector.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/** 
2
 * =================================================================
3
 * Javascript code for OWASP CSRF Protector
4
 * Task it does: Fetch csrftoken from cookie, and attach it to every
5
 *      POST request
6
 *      Allowed GET url
7
 *          -- XHR
8
 *          -- Static Forms
9
 *          -- URLS (GET only)
10
 *          -- dynamic forms
11
 * =================================================================
12
 */
13
14
var CSRFP_FIELD_TOKEN_NAME = 'csrfp_hidden_data_token';
15
var CSRFP_FIELD_URLS = 'csrfp_hidden_data_urls';
16
17
var CSRFP = {
18
    CSRFP_TOKEN: '',
19
    /**
20
     * Array of patterns of url, for which csrftoken need to be added
21
     * In case of GET request also, provided from server
22
     *
23
     * @var string array
24
     */
25
    checkForUrls: [],
26
    /**
27
     * Function to check if a certain url is allowed to perform the request
28
     * With or without csrf token
29
     *
30
     * @param: string, url
31
     *
32
     * @return: boolean,    true if csrftoken is not needed
33
     *                      false if csrftoken is needed
34
     */
35
    _isValidGetRequest: function(url) {
36
        for (var i = 0; i < CSRFP.checkForUrls.length; i++) {
37
            var match = CSRFP.checkForUrls[i].exec(url);
38
            if (match !== null && match.length > 0) {
39
                return false;
40
            }
41
        }
42
        return true;
43
    },
44
    /** 
45
     * function to get Auth key from cookie Andreturn it to requesting function
46
     *
47
     * @param: void
48
     *
49
     * @return: string, csrftoken retrieved from cookie
50
     */
51
    _getAuthKey: function() {
52
        var re = new RegExp(CSRFP.CSRFP_TOKEN +"=([^;]+)(;|$)");
53
        var RegExpArray = re.exec(document.cookie);
54
        
55
        if (RegExpArray === null) {
56
            return false;
57
        }
58
        return RegExpArray[1];
59
    },
60
    /** 
61
     * Function to get domain of any url
62
     *
63
     * @param: string, url
64
     *
65
     * @return: string, domain of url
66
     */
67
    _getDomain: function(url) {
68
        if (url.indexOf("http://") !== 0 
69
            && url.indexOf("https://") !== 0)
70
            return document.domain;
71
        return /http(s)?:\/\/([^\/]+)/.exec(url)[2];
72
    },
73
    /**
74
     * Function to create and return a hidden input element
75
     * For stroing the CSRFP_TOKEN
76
     *
77
     * @param void
78
     *
79
     * @return input element
80
     */
81
    _getInputElt: function() {
82
        var hiddenObj = document.createElement("input");
83
        hiddenObj.name = CSRFP.CSRFP_TOKEN;
84
        hiddenObj.type = 'hidden';
85
        hiddenObj.value = CSRFP._getAuthKey();
86
        return hiddenObj;
87
    },
88
    /**
89
     * Returns absolute path for relative path
90
     * 
91
     * @param base, base url
92
     * @param relative, relative url
93
     *
94
     * @return absolute path (string)
95
     */
96
    _getAbsolutePath: function(base, relative) {
97
        var stack = base.split("/");
98
        var parts = relative.split("/");
99
        // remove current file name (or empty string)
100
        // (omit if "base" is the current folder without trailing slash)
101
        stack.pop(); 
102
             
103
        for (var i = 0; i < parts.length; i++) {
104
            if (parts[i] == ".")
105
                continue;
106
            if (parts[i] == "..")
107
                stack.pop();
108
            else
109
                stack.push(parts[i]);
110
        }
111
        return stack.join("/");
112
    },
113
    /** 
114
     * Remove jcsrfp-token run fun and then put them back 
115
     *
116
     * @param function
117
     * @param reference form obj
118
     *
119
     * @retrun function
120
     */
121
    _csrfpWrap: function(fun, obj) {
122
        return function(event) {
123
            // Remove CSRf token if exists
124
            if (typeof obj[CSRFP.CSRFP_TOKEN] !== 'undefined') {
125
                var target = obj[CSRFP.CSRFP_TOKEN];
126
                target.parentNode.removeChild(target);
127
            }
128
            
129
            // Trigger the functions
130
            var result = fun.apply(this, [event]);
131
            
132
            // Now append the csrfp_token back
133
            obj.appendChild(CSRFP._getInputElt());
134
            
135
            return result;
136
        };
137
    },
138
    /**
139
     * Initialises the CSRFProtector js script
140
     *
141
     * @param void
142
     *
143
     * @return void
144
     */
145
    _init: function() {
146
        CSRFP.CSRFP_TOKEN = document.getElementById(CSRFP_FIELD_TOKEN_NAME).value;
147
        try {
148
            CSRFP.checkForUrls = JSON.parse(document.getElementById(CSRFP_FIELD_URLS).value);
149
        } catch (err) {
150
            console.error(err);
151
            console.error('[ERROR] [CSRF Protector] unable to parse blacklisted url fields.');
152
        }
153
154
        //convert these rules received from php lib to regex objects
155
        for (var i = 0; i < CSRFP.checkForUrls.length; i++) {
156
            CSRFP.checkForUrls[i] = CSRFP.checkForUrls[i].replace(/\*/g, '(.*)')
157
                                .replace(/\//g, "\\/");
158
            CSRFP.checkForUrls[i] = new RegExp(CSRFP.checkForUrls[i]);
159
        }
160
    
161
    }
162
    
163
}; 
164
165
//==========================================================
166
// Adding tokens, wrappers on window onload
167
//==========================================================
168
169
function csrfprotector_init() {
170
    
171
    // Call the init funcion
172
    CSRFP._init();
173
    
174
    //==================================================================
175
    // Adding csrftoken to request resulting from <form> submissions
176
    // Add for each POST, while for mentioned GET request
177
    //==================================================================
178
    for(var i = 0; i < document.forms.length; i++) {
179
        document.forms[i].addEventListener("submit", function(event) {
180
            if (typeof event.target[CSRFP.CSRFP_TOKEN] === 'undefined') {
181
                event.target.appendChild(CSRFP._getInputElt());
182
            } else {
183
                //modify token to latest value
184
                event.target[CSRFP.CSRFP_TOKEN].value = CSRFP._getAuthKey();
185
            }
186
        });
187
    }
188
    
189
    /**
190
     * Add wrapper for HTMLFormElements addEventListener so that any further 
191
     * addEventListens won't have trouble with CSRF token
192
     */
193
    HTMLFormElement.prototype.addEventListener_ = HTMLFormElement.prototype.addEventListener;
194
    HTMLFormElement.prototype.addEventListener = function(eventType, fun, bubble) {
195
        if (eventType === 'submit') {
196
            var wrapped = CSRFP._csrfpWrap(fun, this);
197
            this.addEventListener_(eventType, wrapped, bubble);
198
        } else {
199
            this.addEventListener_(eventType, fun, bubble);
200
        }   
201
    }
202
203
    /**
204
     * Add wrapper for IE's attachEvent
205
     */
206
    if (typeof HTMLFormElement.prototype.attachEvent !== 'undefined') {
207
        HTMLFormElement.prototype.attachEvent_ = HTMLFormElement.prototype.attachEvent;
208
        HTMLFormElement.prototype.attachEvent = function(eventType, fun) {
209
            if (eventType === 'submit') {
210
                var wrapped = CSRFP._csrfpWrap(fun, this);
211
                this.attachEvent_(eventType, wrapped);
212
            } else {
213
                this.attachEvent_(eventType, fun);
214
            }
215
        }
216
    }
217
218
219
    //==================================================================
220
    // Wrapper for XMLHttpRequest & ActiveXObject (for IE 6 & below)
221
    // Set X-No-CSRF to true before sending if request method is 
222
    //==================================================================
223
224
    /** 
225
     * Wrapper to XHR open method
226
     * Add a property method to XMLHttpRequst class
227
     * @param: all parameters to XHR open method
228
     * @return: object returned by default, XHR open method
229
     */
230
    function new_open(method, url, async, username, password) {
231
        this.method = method;
232
        var isAbsolute = (url.indexOf("./") === -1) ? true : false;
233
        if (!isAbsolute) {
234
            var base = location.protocol +'//' +location.host 
235
                            + location.pathname;
236
            url = CSRFP._getAbsolutePath(base, url);
237
        }
238
        if (method.toLowerCase() === 'get' 
239
            && !CSRFP._isValidGetRequest(url)) {
240
            //modify the url
241
            if (url.indexOf('?') === -1) {
242
                url += "?" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey();
243
            } else {
244
                url += "&" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey();
245
            }
246
        }
247
248
        return this.old_open(method, url, async, username, password);
249
    }
250
251
    /** 
252
     * Wrapper to XHR send method
253
     * Add query paramter to XHR object
254
     *
255
     * @param: all parameters to XHR send method
256
     *
257
     * @return: object returned by default, XHR send method
258
     */
259
    function new_send(data) {
260
        if (this.method.toLowerCase() === 'post') {
261
            if (data !== null && typeof data === 'object') {
262
                data[CSRFP.CSRFP_TOKEN] = CSRFP._getAuthKey();
263
            } else {
264
                if (typeof data != "undefined") {
265
                    data += "&";
266
                } else {
267
                    data = "";
268
                }
269
                data += CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey();
270
            }
271
        }
272
        return this.old_send(data);
273
    }
274
275
    if (window.XMLHttpRequest) {
276
        // Wrapping
277
        XMLHttpRequest.prototype.old_send = XMLHttpRequest.prototype.send;
278
        XMLHttpRequest.prototype.old_open = XMLHttpRequest.prototype.open;
279
        XMLHttpRequest.prototype.open = new_open;
280
        XMLHttpRequest.prototype.send = new_send;
281
    }
282
    if (typeof ActiveXObject !== 'undefined') {
283
        ActiveXObject.prototype.old_send = ActiveXObject.prototype.send;
284
        ActiveXObject.prototype.old_open = ActiveXObject.prototype.open;
285
        ActiveXObject.prototype.open = new_open;
286
        ActiveXObject.prototype.send = new_send;    
287
    }
288
    //==================================================================
289
    // Rewrite existing urls ( Attach CSRF token )
290
    // Rules:
291
    // Rewrite those urls which matches the regex sent by Server
292
    // Ignore cross origin urls & internal links (one with hashtags)
293
    // Append the token to those url already containig GET query parameter(s)
294
    // Add the token to those which does not contain GET query parameter(s)
295
    //==================================================================
296
297
    for (var i = 0; i < document.links.length; i++) {
298
        document.links[i].addEventListener("mousedown", function(event) {
299
            var href = event.target.href;
300
            if(typeof href === "string")
301
            {
302
                var urlDisect = href.split('#');
303
                var url = urlDisect[0];
304
                var hash = urlDisect[1];
305
306
                if(CSRFP._getDomain(url).indexOf(document.domain) === -1
307
                    || CSRFP._isValidGetRequest(url)) {
308
                    //cross origin or not to be protected by rules -- ignore
309
                    return;
310
                }
311
312
                if (url.indexOf('?') !== -1) {
313
                    if(url.indexOf(CSRFP.CSRFP_TOKEN) === -1) {
314
                        url += "&" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey();
315
                    } else {
316
                        url = url.replace(new RegExp(CSRFP.CSRFP_TOKEN +"=.*?(&|$)", 'g'),
317
                            CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey() + "$1");
318
                    }
319
                } else {
320
                    url += "?" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey();
321
                }
322
323
                event.target.href = url;
324
                if (typeof hash !== 'undefined') {
325
                    event.target.href += '#' +hash;
326
                }
327
            }
328
        });
329
    }
330
331
}
332
333
window.addEventListener("DOMContentLoaded", function() {
334
    csrfprotector_init();
335
}, false);
336