web2py / web2py

Compare 93297fe ... +1 ... 748bc8a

No flags found

Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.

e.g., #unittest #integration

#production #enterprise

#frontend #backend

Learn more about Codecov Flags here.

Showing 3 of 5 files from the diff.
Newly tracked file
gluon/form.py created.
Newly tracked file
gluon/digest.py created.
Other files ignored by Codecov

@@ -0,0 +1,228 @@
Loading
1 +
from gluon.dal import DAL
2 +
from gluon.storage import Storage
3 +
from gluon.utils import web2py_uuid
4 +
try:
5 +
    # web3py
6 +
    from gluon.current import current
7 +
    from gluon.url import URL
8 +
    from gluon.helpers import *
9 +
except:
10 +
    # web2py
11 +
    from gluon import current
12 +
    from gluon.html import *
13 +
14 +
15 +
16 +
def FormStyleDefault(table, vars, errors, readonly, deletable):
17 +
18 +
    form = FORM(TABLE(),_method='POST',_action='#',_enctype='multipart/form-data')
19 +
    for field in table:
20 +
21 +
        input_id = '%s_%s' % (field.tablename, field.name)
22 +
        value = field.formatter(vars.get(field.name))
23 +
        error = errors.get(field.name)
24 +
        field_class = field.type.split()[0].replace(':','-')
25 +
26 +
        if field.type == 'blob': # never display blobs (mistake?)
27 +
            continue
28 +
        elif readonly or field.type=='id':
29 +
            if not field.readable:
30 +
                continue
31 +
            else:
32 +
                control = field.represent and field.represent(value) or value or ''
33 +
        elif not field.writable:
34 +
            continue
35 +
        elif field.widget:
36 +
            control = field.widget(table, value)
37 +
        elif field.type == 'text':
38 +
            control = TEXTAREA(value or '', _id=input_id,_name=field.name)
39 +
        elif field.type == 'boolean':
40 +
            control = INPUT(_type='checkbox', _id=input_id, _name=field.name,
41 +
                            _value='ON', _checked = value)
42 +
        elif field.type == 'upload':
43 +
            control = DIV(INPUT(_type='file', _id=input_id, _name=field.name))
44 +
            if value:
45 +
                control.append(A('download',
46 +
                                 _href=URL('default','download',args=value)))
47 +
                control.append(INPUT(_type='checkbox',_value='ON',
48 +
                                     _name='_delete_'+field.name))
49 +
                control.append('(check to remove)')
50 +
        elif hasattr(field.requires, 'options'):
51 +
            multiple = field.type.startswith('list:')
52 +
            value = value if isinstance(value, list) else [value]
53 +
            options = [OPTION(v,_value=k,_selected=(k in value))
54 +
                       for k,v in field.requires.options()]
55 +
            control = SELECT(*options, _id=input_id, _name=field.name,
56 +
                              _multiple=multiple)
57 +
        else:
58 +
            field_type = 'password' if field.type == 'password' else 'text'
59 +
            control = INPUT(_type=field_type, _id=input_id, _name=field.name,
60 +
                            _value=value, _class=field_class)
61 +
62 +
        form[0].append(TR(TD(LABEL(field.label,_for=input_id)),
63 +
                          TD(control,DIV(error,_class='error') if error else ''),
64 +
                          TD(field.comment or '')))
65 +
66 +
    td = TD(INPUT(_type='submit',_value='Submit'))
67 +
    if deletable:
68 +
        td.append(INPUT(_type='checkbox',_value='ON',_name='_delete'))
69 +
        td.append('(check to delete)')
70 +
    form[0].append(TR(TD(),td,TD()))
71 +
    return form
72 +
73 +
# ################################################################
74 +
# Form object (replaced SQLFORM)
75 +
# ################################################################
76 +
77 +
class Form(object):
78 +
    """
79 +
    Usage in web2py controller:
80 +
81 +
       def index():
82 +
           form = Form(db.thing, record=1)
83 +
           if form.accepted: ...
84 +
           elif form.errors: ...
85 +
           else: ...
86 +
           return dict(form=form)
87 +
88 +
    Arguments:
89 +
    - table: a DAL table or a list of fields (equivalent to old SQLFORM.factory)
90 +
    - record: a DAL record or record id
91 +
    - readonly: set to True to make a readonly form
92 +
    - deletable: set to False to disallow deletion of record
93 +
    - formstyle: a function that renders the form using helpers (FormStyleDefault)
94 +
    - dbio: set to False to prevent any DB write
95 +
    - keepvalues: (NOT IMPLEMENTED)
96 +
    - formname: the optional name of this form
97 +
    - csrf: set to False to disable CRSF protection
98 +
    """
99 +
100 +
    def __init__(self,
101 +
                 table,
102 +
                 record=None,
103 +
                 readonly=False,
104 +
                 deletable=True,
105 +
                 formstyle=FormStyleDefault,
106 +
                 dbio=True,
107 +
                 keepvalues=False,
108 +
                 formname=False,
109 +
                 hidden=None,
110 +
                 csrf=True):
111 +
112 +
        if isinstance(table, list):
113 +
            dbio = False
114 +
            # mimic a table from a list of fields without calling define_table
115 +
            formname = formname or 'none'
116 +
            for field in table: field.tablename = getattr(field,'tablename',formname)
117 +
118 +
        if isinstance(record, (int, long, basestring)):
119 +
            record_id = int(str(record))
120 +
            self.record = table[record_id]
121 +
        else:
122 +
            self.record = record
123 +
124 +
        self.table = table
125 +
        self.readonly = readonly
126 +
        self.deletable = deletable and not readonly and self.record
127 +
        self.formstyle = formstyle
128 +
        self.dbio = dbio
129 +
        self.keepvalues = True if keepvalues or self.record else False
130 +
        self.csrf = csrf
131 +
        self.vars = Storage()
132 +
        self.errors = Storage()
133 +
        self.submitted = False
134 +
        self.deleted = False
135 +
        self.accepted = False
136 +
        self.cached_helper = False
137 +
        self.formname = formname or table._tablename
138 +
        self.hidden = hidden
139 +
        self.formkey = None
140 +
141 +
        request = current.request
142 +
143 +
        if readonly or request.method=='GET':
144 +
            if self.record:
145 +
                self.vars = self.record
146 +
        else:
147 +
            post_vars = request.post_vars
148 +
            print post_vars
149 +
            self.submitted = True
150 +
            # check for CSRF
151 +
            if csrf and self.formname in (current.session._formkeys or {}):
152 +
                self.formkey = current.session._formkeys[self.formname]
153 +
            # validate fields
154 +
            if not csrf or post_vars._formkey == self.formkey:
155 +
                if not post_vars._delete:
156 +
                    for field in self.table:
157 +
                        if field.writable:
158 +
                            value = post_vars.get(field.name)
159 +
                            # FIX THIS deal with set_self_id before validate
160 +
                            (value, error) = field.validate(value)
161 +
                            if field.type == 'upload':
162 +
                                delete = post_vars.get('_delete_'+field.name)
163 +
                                if value is not None and hasattr(value,'file'):
164 +
                                    value = field.store(value.file,
165 +
                                                        value.filename,
166 +
                                                        field.uploadfolder)
167 +
                                elif self.record and not delete:
168 +
                                    value = self.record.get(field.name)
169 +
                                else:
170 +
                                    value = None
171 +
                            self.vars[field.name] = value
172 +
                            if error:
173 +
                                self.errors[field.name] = error
174 +
                    if self.record:
175 +
                        self.vars.id = self.record.id
176 +
                    if not self.errors:
177 +
                        self.accepted = True
178 +
                        if dbio:
179 +
                            self.update_or_insert()
180 +
                elif dbio:
181 +
                    self.deleted = True
182 +
                    self.record.delete_record()
183 +
        # store key for future CSRF
184 +
        if csrf:
185 +
            session = current.session
186 +
            if not session._formkeys:
187 +
                session._formkeys = {}
188 +
            if self.formname not in current.session._formkeys:
189 +
                session._formkeys[self.formname] = web2py_uuid()
190 +
            self.formkey = session._formkeys[self.formname]
191 +
192 +
    def update_or_insert(self):
193 +
        if self.record:
194 +
            self.record.update_record(**self.vars)
195 +
        else:
196 +
            # warning, should we really insert if record
197 +
            self.vars.id = self.table.insert(**self.vars)
198 +
199 +
    def clear():
200 +
        self.vars.clear()
201 +
        self.errors.clear()
202 +
        for field in self.table:
203 +
            self.vars[field.name] = field.default
204 +
205 +
    def helper(self):
206 +
        if not self.cached_helper:
207 +
            cached_helper = self.formstyle(self.table,
208 +
                                           self.vars,
209 +
                                           self.errors,
210 +
                                           self.readonly,
211 +
                                           self.deletable)
212 +
            if self.csrf:
213 +
                cached_helper.append(INPUT(_type='hidden',_name='_formkey',
214 +
                                           _value=self.formkey))
215 +
            for key in self.hidden or {}:
216 +
                cached_helper.append(INPUT(_type='hidden',_name=key,
217 +
                                           _value=self.hidden[key]))
218 +
            self.cached_helper = cached_helper
219 +
        return cached_helper
220 +
221 +
    def xml(self):
222 +
        return self.helper().xml()
223 +
224 +
    def __unicode__(self):
225 +
        return self.xml()
226 +
227 +
    def __str__(self):
228 +
        return self.xml().encode('utf8')

@@ -0,0 +1,62 @@
Loading
1 +
#!/usr/bin/env python
2 +
# -*- coding: utf-8 -*-
3 +
4 +
"""
5 +
| This file is part of the web2py Web Framework
6 +
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 +
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8 +
9 +
This file specifically includes utilities for security.
10 +
--------------------------------------------------------
11 +
"""
12 +
13 +
import hashlib
14 +
import hmac
15 +
from gluon._compat import basestring, pickle, PY2, xrange, to_bytes, to_native
16 +
17 +
def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc=None):
18 +
    hashfunc = hashfunc or sha1
19 +
    hmac = hashlib.pbkdf2_hmac(hashfunc().name, to_bytes(data),
20 +
                               to_bytes(salt), iterations, keylen)
21 +
    return binascii.hexlify(hmac)
22 +
23 +
24 +
def simple_hash(text, key='', salt='', digest_alg='md5'):
25 +
    """Generate hash with the given text using the specified digest algorithm."""
26 +
    text = to_bytes(text)
27 +
    key = to_bytes(key)
28 +
    salt = to_bytes(salt)
29 +
    if not digest_alg:
30 +
        raise RuntimeError("simple_hash with digest_alg=None")
31 +
    elif not isinstance(digest_alg, str):  # manual approach
32 +
        h = digest_alg(text + key + salt)
33 +
    elif digest_alg.startswith('pbkdf2'):  # latest and coolest!
34 +
        iterations, keylen, alg = digest_alg[7:-1].split(',')
35 +
        return to_native(pbkdf2_hex(text, salt, int(iterations),
36 +
                                    int(keylen), get_digest(alg)))
37 +
    elif key:  # use hmac
38 +
        digest_alg = get_digest(digest_alg)
39 +
        h = hmac.new(key + salt, text, digest_alg)
40 +
    else:  # compatible with third party systems
41 +
        h = get_digest(digest_alg)()
42 +
        h.update(text + salt)
43 +
    return h.hexdigest()
44 +
45 +
46 +
def get_digest(value):
47 +
    """Return a hashlib digest algorithm from a string."""
48 +
    if isinstance(value, str):
49 +
        value = value.lower()
50 +
        if value not in ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'):
51 +
            raise ValueError("Invalid digest algorithm: %s" % value)
52 +
        value = getattr(hashlib, value)
53 +
    return value
54 +
55 +
DIGEST_ALG_BY_SIZE = {
56 +
    128 // 4: 'md5',
57 +
    160 // 4: 'sha1',
58 +
    224 // 4: 'sha224',
59 +
    256 // 4: 'sha256',
60 +
    384 // 4: 'sha384',
61 +
    512 // 4: 'sha512',
62 +
}

@@ -105,6 +105,22 @@
Loading
105 105
            return url
106 106
    return URL(url)
107 107
108 +
REGEX_OPEN_REDIRECT = re.compile(r"^(\w+)?[:]?(/$|//.*|/\\.*|[~]/.*)")
109 +
110 +
def prevent_open_redirect(url):
111 +
    # Prevent an attacker from adding an arbitrary url after the
112 +
    # _next variable in the request.
113 +
    host = current.request.env.http_host
114 +
    print(host)
115 +
    if not url:
116 +
        return None
117 +
    if REGEX_OPEN_REDIRECT.match(url):
118 +
        parts = url.split('/')
119 +
        if len(parts) > 2 and parts[2] == host:
120 +
            return url
121 +
        return None
122 +
    return url
123 +
108 124
109 125
class Mail(object):
110 126
    """
@@ -1750,25 +1766,12 @@
Loading
1750 1766
1751 1767
    def get_vars_next(self):
1752 1768
        next = current.request.vars._next
1753 -
        host = current.request.env.http_host
1754 1769
        if isinstance(next, (list, tuple)):
1755 1770
            next = next[0]
1756 1771
        if next and self.settings.prevent_open_redirect_attacks:
1757 -
            return self.prevent_open_redirect(next, host)
1772 +
            return prevent_open_redirect(next)
1758 1773
        return next or None
1759 1774
1760 -
    @staticmethod
1761 -
    def prevent_open_redirect(next, host):
1762 -
        # Prevent an attacker from adding an arbitrary url after the
1763 -
        # _next variable in the request.
1764 -
        if next:
1765 -
            parts = next.split('/')
1766 -
            if ':' not in parts[0] and parts[:2] != ['', '']:
1767 -
                return next
1768 -
            elif len(parts) > 2 and parts[0].endswith(':') and parts[1:3] == ['', host]:
1769 -
                return next
1770 -
        return None
1771 -
1772 1775
    def table_cas(self):
1773 1776
        return self.db[self.settings.table_cas_name]
1774 1777
@@ -4274,8 +4277,8 @@
Loading
4274 4277
        if request.extension == 'json' and request.vars.json:
4275 4278
            request.vars.update(json.loads(request.vars.json))
4276 4279
        if next is DEFAULT:
4277 -
            next = request.get_vars._next \
4278 -
                or request.post_vars._next \
4280 +
            next = prevent_open_redirect(request.get_vars._next) \
4281 +
                or prevent_open_redirect(request.post_vars._next) \
4279 4282
                or self.settings.update_next
4280 4283
        if onvalidation is DEFAULT:
4281 4284
            onvalidation = self.settings.update_onvalidation
@@ -4420,8 +4423,8 @@
Loading
4420 4423
        request = current.request
4421 4424
        session = current.session
4422 4425
        if next is DEFAULT:
4423 -
            next = request.get_vars._next \
4424 -
                or request.post_vars._next \
4426 +
            next = prevent_open_redirect(request.get_vars._next) \
4427 +
                or prevent_open_redirect(request.post_vars._next) \
4425 4428
                or self.settings.delete_next
4426 4429
        if message is DEFAULT:
4427 4430
            message = self.messages.record_deleted

Learn more Showing 3 files with coverage changes found.

New file gluon/form.py
New
Loading file...
New file gluon/digest.py
New
Loading file...
Changes in gluon/sqlhtml.py
-3
+2
+1
Loading file...
Files Coverage
gluon -0.43% 41.69%
Project Totals (45 files) 41.69%
Loading