After enabling this option, the first time a user successfully logs in they will be required scan a QR code with their Google Authenticator app in order to setup their Google Authenticator secret code.
190 |
191 |
To login with Google Authenticator, users must use the Google Authenticator app:
196 |
197 | ]]>
198 |
199 |
200 |
201 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/app/code/community/HE/TwoFactorAuth/readme.txt:
--------------------------------------------------------------------------------
1 | # Sentry
2 | ## Magento Two Factor Authentication Module
3 |
4 | ### Authors
5 | - Greg Croasdill, Human Element, Inc http://www.human-element.com
6 | - Gregg Milligan, Human Element, Inc http://www.human-element.com
7 | - Aric Watson, Nexcess.net https://www.nexcess.net/
8 |
9 | ### License
10 | - GPL -- https://www.gnu.org/copyleft/gpl.html
11 |
12 | ### Purpose
13 | Sentry Two-Factor Authentication will protect your Magento store and customer data by adding an extra check to authenticate
14 | your Admin users before allowing them access. Developed as a partnership between the Human Element Magento Development team
15 | and Nexcess Hosting, Sentry Two-Factor Authentication for Magento is easy to setup and admin users can quickly login.
16 |
17 | ### Supported Providers (more to come)
18 | The following __Two Factor Authentication__ providers are supported at this time.
19 |
20 | #### Duo Security
21 | For more information on Duo security's API, please see -
22 | - https://www.duosecurity.com
23 |
24 | #### Google Authenticator
25 | For more information on Google Authenticator, please see -
26 | - https://github.com/google/google-authenticator/wiki
27 | - https://support.google.com/accounts/answer/180744?hl=en&ref_topic=1099588
28 |
29 | ### Referanced work
30 |
31 | Some code based on previous work by Jonathan Day jonathan@aligent.com.au
32 | - https://github.com/magento-hackathon/Magento-Two-factor-Authentication
33 |
34 | Some code based on previous work by Michael Kliewe/PHPGangsta
35 | - https://github.com/PHPGangsta/GoogleAuthenticator
36 | - http://www.phpgangsta.de/
37 |
38 | ----
39 | ### Notes -
40 | 1. Installing this module will update the AdminUser table in the Magento database to add a twofactor_google_secret
41 | field for storing the local GA key. It is safe to remove this field once the module is removed.
42 |
43 | 2. If you get locked out of admin because of a settings issue, loss of your provider account or other software related issue, you can *temporarily disable* the second factor authentication -
44 | - Place a file named __tfaoff.flag__ in the root directory of your Magento installation.
45 | - Login to Magento's Admin area without the second factor.
46 | - Update settings or disable Sentry
47 | - Remove the tfaoff.flag file to re-enable two factor authentication.
48 |
--------------------------------------------------------------------------------
/app/code/community/HE/TwoFactorAuth/sql/he_twofactorauth/mysql4-install-1.0.2.php:
--------------------------------------------------------------------------------
1 | startSetup();
6 | $installer->getConnection()
7 | ->addColumn($installer->getTable('admin/user'),
8 | 'twofactor_google_secret',
9 | array(
10 | 'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
11 | 'nullable' => true,
12 | 'length' => 255,
13 | 'default' => null,
14 | 'comment' => 'Google Secret'
15 | )
16 | );
17 | $installer->endSetup();
18 |
19 | // TODO add an uninstall script for users who remove module - apparently no automatic way to do this
--------------------------------------------------------------------------------
/app/design/adminhtml/default/default/layout/he_twofactor/auth.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | skin_csshe_twofactor/css/admin.css
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/design/adminhtml/default/default/template/he_twofactor/duo/auth.phtml:
--------------------------------------------------------------------------------
1 | getUser()->getUsername();
4 | $sign_request = Mage::getModel("he_twofactorauth/validate_duo")->signRequest($user);
5 | $auth_host = Mage::getModel("he_twofactorauth/validate_duo")->getHost();
6 |
7 | $ready = ($user && $sign_request && $auth_host); // all have to be set
8 |
9 | ?>
10 |
11 |
12 |
13 |
14 | __('Duo Two Factor Authentication'); ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | getMessagesBlock()->getGroupedHtml() ?>
33 |
34 |
35 |
36 |
44 |
45 |
46 |
47 | The Duo Security Two-Factor Authentication is not configured correctly and has been disabled.
48 | Please use the link below to login and review the settings.
49 |
50 | __('Return to the Login screen') ?>
51 |
Multifactor authentication provides an additional check when logging into your Magento administration system.
40 | Once configured, an additional challenge screen is added to the admin login process. This screen will interact
41 | with an additional method of contact, usually a cellphone or email, that will prove that the user attempting login is the real user.
42 |
NOTE - when using multifactor authentication it is highly advised that each admin user have their own account.
43 |
To find out more about Human Element and Magento Support Services, please visit our website at
44 |
45 | or fill in our contact form.
46 | To find out more about Nexcess Hosting, visit their website at
47 | .
48 |
49 |
--------------------------------------------------------------------------------
/app/etc/modules/HE_TwoFactorAuth.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | community
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build/build_package.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2.7
2 | # -*- coding: utf-8 -*-
3 |
4 | # Nexcess.net Turpentine Extension for Magento
5 | # Copyright (C) 2012 Nexcess.net L.L.C.
6 |
7 | # This program is free software; you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation; either version 2 of the License, or
10 | # (at your option) any later version.
11 |
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 |
17 | # You should have received a copy of the GNU General Public License along
18 | # with this program; if not, write to the Free Software Foundation, Inc.,
19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 |
21 | """Script to generate Magento Extension package files
22 |
23 | Run as: build_package.py
24 |
25 | You can override the xmllint and PHP binaries used for syntax checks with the
26 | TURPENTINE_BIN_PHP and TURPENTINE_BIN_XMLLINT environment variables. Useful for
27 | checking with a non-default version of PHP.
28 | """
29 |
30 | __title__ = 'build_package.py'
31 | __version__ = '0.0.3'
32 | __author__ = 'Alex Headley '
33 | __license__ = 'GPLv2'
34 | __copyright__ = 'Copyright (C) 2012 Nexcess.net L.L.C.'
35 |
36 | import os
37 | import xml.etree.ElementTree as ElementTree
38 | import logging
39 | import datetime
40 | import hashlib
41 | import re
42 | import tarfile
43 | import subprocess
44 |
45 | class Magento_Packager(object):
46 | BIN_PHP = os.environ.get('TURPENTINE_BIN_PHP', 'php')
47 | BIN_XMLLINT = os.environ.get('TURPENTINE_BIN_XMLLINT', 'xmllint')
48 | BIN_BASH = os.environ.get('TURPENTINE_BIN_BASH', 'bash')
49 | BIN_GCC = os.environ.get('TURPENTINE_BIN_GCC', 'gcc')
50 |
51 | TARGET_DIRS = {
52 | 'magelocal': 'app/code/local',
53 | 'magejs': 'js',
54 | 'magelib': 'lib',
55 | 'magecommunity': 'app/code/community',
56 | 'magecore': 'app/code/core',
57 | 'magedesign': 'app/design',
58 | 'mageetc': 'app/etc',
59 | }
60 | MAGE_PKG_XML_FILENAME = 'package.xml'
61 |
62 | def __init__(self, base_dir, debug=False):
63 | self._base_dir = base_dir
64 | self._logger = logging.getLogger(self.__class__.__name__)
65 | if debug:
66 | self._logger.setLevel(logging.DEBUG)
67 | else:
68 | self._logger.setLevel(logging.INFO)
69 | self._file_list = []
70 | self._logger.debug('Packager init with base dir: %s', self._base_dir)
71 | self._logger.debug('Using PHP binary: %s', self.BIN_PHP)
72 | self._logger.debug('Using xmllint binary: %s', self.BIN_XMLLINT)
73 |
74 | def do_syntax_check(self):
75 | self._logger.info('Running syntax check on %d files', len(self._file_list))
76 | result = True
77 | syntax_map = {
78 | '.php': self._php_syntax_check,
79 | '.phtml': self._php_syntax_check,
80 | '.xml': self._xml_syntax_check,
81 | '.sh': self._bash_syntax_check,
82 | '.bash': self._bash_syntax_check,
83 | '.c': self._gcc_syntax_check,
84 | }
85 | def unsupported_syntax_check(filename):
86 | self._logger.debug('Skipping syntax check for unsupported file: %s',
87 | filename)
88 | return True
89 |
90 | for filename in self._file_list:
91 | syntax_check = syntax_map.get(os.path.splitext(filename)[1].lower(),
92 | unsupported_syntax_check)
93 | if not syntax_check(filename):
94 | self._logger.warning('Syntax check failed for file: %s', filename)
95 | result = False
96 | return result
97 |
98 | def build_package_xml(self, connect_file):
99 | self._logger.info('Building package from connect file: %s', connect_file)
100 | connect_dom = ElementTree.parse(connect_file)
101 | ext_name = connect_dom.find('name').text
102 | self._logger.debug('Using "%s" as extension name', ext_name)
103 | config_dom = self._get_config_dom(ext_name, connect_dom.find('channel').text)
104 | module_dom = self._get_module_dom(ext_name)
105 |
106 | self._logger.info('Building extension %s version %s', ext_name,
107 | config_dom.find('modules/%s/version' % ext_name).text)
108 |
109 | if connect_dom.find('channel').text != \
110 | module_dom.find('modules/%s/codePool' % ext_name).text:
111 | self._logger.warning('Connect file code pool (%s) does not match module code pool (%s)',
112 | connect_dom.find('channel').text,
113 | module_dom.find('modules/%s/codePool' % ext_name).text)
114 |
115 | pkg_dom = self._build_package_dom(ElementTree.Element('package'),
116 | connect_dom, config_dom, module_dom)
117 |
118 | self._logger.info('Finished building extension package XML')
119 |
120 | return pkg_dom
121 |
122 | def build_tarball(self, pkg_xml, tarball_name=None, keep_pkg_xml=False):
123 | manifest_filename = '%s/build/manifest-%s.xml' % \
124 | (self._base_dir, pkg_xml.findtext('./version'))
125 | if tarball_name is None:
126 | tarball_name = '%s/build/%s-%s.tgz' % (self._base_dir,
127 | pkg_xml.findtext('./name'), pkg_xml.findtext('./version'))
128 | self._logger.info('Writing tarball to: %s', tarball_name)
129 | cdir = os.getcwd()
130 | os.chdir(self._base_dir)
131 | with open(manifest_filename, 'w') as xml_file:
132 | ElementTree.ElementTree(pkg_xml).write(xml_file, 'utf-8', True)
133 | self._logger.debug('Wrote package XML')
134 | with tarfile.open(tarball_name, 'w:gz') as tarball:
135 | for filename in self._file_list:
136 | alt_filename = filename.replace(self._base_dir + '/', '')
137 | self._logger.debug('Adding file to tarball: %s', alt_filename)
138 | tarball.add(filename, alt_filename)
139 | self._logger.debug('Adding file to tarball: %s',
140 | self.MAGE_PKG_XML_FILENAME)
141 | tarball.add(manifest_filename, self.MAGE_PKG_XML_FILENAME)
142 | self._logger.info('Finished writing tarball')
143 | if not keep_pkg_xml:
144 | os.unlink(manifest_filename)
145 | os.chdir(cdir)
146 | return tarball_name
147 |
148 | def _build_package_dom(self, pkg_dom, connect_dom, config_dom, module_dom):
149 | ext_name = connect_dom.find('name').text
150 | now = datetime.datetime.now()
151 | commit_hash = self._get_git_hash()
152 | self._logger.debug('Using commit hash: %s', commit_hash)
153 | extension = {
154 | 'name': ext_name,
155 | 'version': config_dom.find('modules/%s/version' % ext_name).text,
156 | 'stability': connect_dom.find('stability').text,
157 | 'license': connect_dom.find('license').text,
158 | 'channel': connect_dom.find('channel').text,
159 | 'extends': None,
160 | 'summary': connect_dom.find('summary').text,
161 | 'description': connect_dom.find('description').text,
162 | 'notes': connect_dom.find('notes').text,
163 | 'authors': None,
164 | 'date': now.date().isoformat(),
165 | 'time': now.time().strftime('%H:%M:%S'),
166 | 'contents': None,
167 | 'compatibile': None,
168 | 'dependencies': None,
169 | '__packager': '%s v%s' % (__title__, __version__),
170 | '__commit_hash': commit_hash,
171 | }
172 | for key, value in extension.iteritems():
173 | tag = ElementTree.SubElement(pkg_dom, key)
174 | if value:
175 | tag.text = value
176 | self._logger.debug('Added package element <%s> = "%s"', key, value)
177 |
178 | pkg_dom.find('license').set('uri', connect_dom.find('license_uri').text)
179 | self._build_authors_tag(pkg_dom.find('authors'), connect_dom)
180 | self._build_contents_tag(pkg_dom.find('contents'), connect_dom)
181 | self._build_dependencies_tag(pkg_dom.find('dependencies'), connect_dom)
182 | return pkg_dom
183 |
184 | def _build_authors_tag(self, authors_tag, connect_dom):
185 | for i, _ in enumerate(connect_dom.findall('authors/name/name')):
186 | author_tag = ElementTree.SubElement(authors_tag, 'author')
187 | name_tag = ElementTree.SubElement(author_tag, 'name')
188 | name_tag.text = list(connect_dom.findall('authors/name/name'))[i].text
189 | user_tag = ElementTree.SubElement(author_tag, 'user')
190 | user_tag.text = list(connect_dom.findall('authors/user/user'))[i].text
191 | email_tag = ElementTree.SubElement(author_tag, 'email')
192 | email_tag.text = list(connect_dom.findall('authors/email/email'))[i].text
193 | self._logger.info('Added author %s (%s) <%s>', name_tag.text,
194 | user_tag.text, email_tag.text)
195 | return authors_tag
196 |
197 | def _build_contents_tag(self, contents_tag, connect_dom):
198 | used_target_paths = list(set(el.text for el in connect_dom.findall('contents/target/target')))
199 | targets = list(self._iterate_targets(connect_dom))
200 | for target_path_name in used_target_paths:
201 | target_tag = ElementTree.SubElement(contents_tag, 'target')
202 | target_tag.set('name', target_path_name)
203 | self._logger.debug('Adding objects for target: %s', target_path_name)
204 | for target in (t for t in targets if t['target'] == target_path_name):
205 | if target['type'] == 'dir':
206 | self._logger.info('Recursively adding dir: %s::%s',
207 | target['target'], target['path'])
208 | for obj_path, obj_name, obj_hash in self._walk_path(os.path.join(
209 | self._base_dir, self.TARGET_DIRS[target['target']], target['path']),
210 | target['include'], target['ignore']):
211 | parent_tag = self._make_parent_tags(target_tag, obj_path.replace(
212 | os.path.join(self._base_dir, self.TARGET_DIRS[target['target']]), '').strip('/'))
213 | if obj_hash is None:
214 | obj_tag = ElementTree.SubElement(parent_tag, 'dir')
215 | obj_tag.set('name', obj_name)
216 | self._logger.debug('Added directory: %s', obj_name)
217 | else:
218 | obj_tag = ElementTree.SubElement(parent_tag, 'file')
219 | obj_tag.set('name', obj_name)
220 | obj_tag.set('hash', obj_hash)
221 | self._file_list.append(os.path.join(obj_path, obj_name))
222 | self._logger.debug('Added file: %s (%s)', obj_name, obj_hash)
223 | else:
224 | parent_tag = self._make_parent_tags(target_tag, os.path.dirname(target['path']))
225 | obj_name = os.path.basename(target['path'])
226 | obj_hash = self._get_file_hash(os.path.join(
227 | self._base_dir, self.TARGET_DIRS[target['target']],
228 | target['path']))
229 | obj_tag = ElementTree.SubElement(parent_tag, 'file')
230 | obj_tag.set('name', obj_name)
231 | obj_tag.set('hash', obj_hash)
232 | self._file_list.append(os.path.join(self._base_dir,
233 | self.TARGET_DIRS[target['target']], target['path']))
234 | self._logger.info('Added single file: %s::%s (%s)',
235 | target['target'], target['path'], obj_hash)
236 | self._logger.debug('Finished adding targets')
237 | return contents_tag
238 |
239 | def _make_parent_tags(self, target_tag, tag_path):
240 | if tag_path:
241 | parts = tag_path.split('/')
242 | current_node = target_tag
243 | for part in parts:
244 | new_node = current_node.find('dir[@name=\'%s\']' % part)
245 | if new_node is None:
246 | new_node = ElementTree.SubElement(current_node, 'dir')
247 | new_node.set('name', part)
248 | current_node = new_node
249 | return current_node
250 | else:
251 | return target_tag
252 |
253 | def _iterate_targets(self, connect_dom):
254 | for i, el in enumerate(connect_dom.findall('contents/target/target')):
255 | yield {
256 | 'target': connect_dom.find('contents/target').getchildren()[i].text,
257 | 'path': connect_dom.find('contents/path').getchildren()[i].text,
258 | 'type': connect_dom.find('contents/type').getchildren()[i].text,
259 | 'include': connect_dom.find('contents/include').getchildren()[i].text,
260 | 'ignore': connect_dom.find('contents/ignore').getchildren()[i].text,
261 | }
262 |
263 | def _get_file_hash(self, filename):
264 | with open(filename, 'rb') as f:
265 | return hashlib.md5(f.read()).hexdigest()
266 |
267 | def _walk_path(self, path, include, ignore):
268 | for dirpath, dirnames, filenames in os.walk(path):
269 | for filename in filenames:
270 | if (include and re.match(include[1:-1], filename) and not \
271 | (ignore and re.match(ignore[1:-1], filename))):
272 | yield dirpath, filename, self._get_file_hash(os.path.join(dirpath, filename))
273 | for dirname in dirnames:
274 | if (include and re.match(include[1:-1], dirname) and not \
275 | (ignore and re.match(ignore[1:-1], dirname))):
276 | yield dirpath, dirname, None
277 |
278 | def _build_dependencies_tag(self, dependencies_tag, connect_dom):
279 | req_tag = ElementTree.SubElement(dependencies_tag, 'required')
280 | php_tag = ElementTree.SubElement(req_tag, 'php')
281 | min_tag = ElementTree.SubElement(php_tag, 'min')
282 | min_tag.text = connect_dom.findtext('depends_php_min')
283 | max_tag = ElementTree.SubElement(php_tag, 'max')
284 | max_tag.text = connect_dom.findtext('depends_php_max')
285 | self._logger.debug('Finished adding dependencies')
286 | return dependencies_tag
287 |
288 | def _get_module_dom(self, ext_name):
289 | fn = os.path.join(self._base_dir, 'app/etc/modules', ext_name + '.xml')
290 | self._logger.debug('Using extension config file: %s', fn)
291 | return ElementTree.parse(fn)
292 |
293 | def _get_config_dom(self, ext_name, codepool):
294 | ns, ext = ext_name.split('_', 2)
295 | fn = os.path.join(self._base_dir, 'app/code', codepool, ns, ext, 'etc', 'config.xml')
296 | self._logger.debug('Using extension module file: %s', fn)
297 | return ElementTree.parse(fn)
298 |
299 | def _get_git_hash(self):
300 | """Get the current git commit hash
301 |
302 | Blatently stolen from:
303 | https://github.com/overviewer/Minecraft-Overviewer/blob/master/overviewer_core/util.py#L40
304 | """
305 | try:
306 | with open(os.path.join(self._base_dir, '.git', 'HEAD'), 'r') as head_file:
307 | ref = head_file.read().strip()
308 | if ref[:5] == 'ref: ':
309 | with open(os.path.join(self._base_dir, '.git', ref[5:]), 'r') as commit_file:
310 | return commit_file.read().strip()
311 | else:
312 | return ref[5:]
313 | except Exception as err:
314 | self._logger.warning('Couldnt read the git commit hash: %s :: %s',
315 | err.__class__.__name__, err)
316 | return 'UNKNOWN'
317 |
318 | def _php_syntax_check(self, filename):
319 | self._logger.debug('Checking PHP syntax for file: %s', filename)
320 | return self._run_quiet(self.BIN_PHP, '-l', filename)
321 |
322 | def _xml_syntax_check(self, filename):
323 | self._logger.debug('Checking XML syntax for file: %s', filename)
324 | return self._run_quiet(self.BIN_XMLLINT, '--format', filename)
325 |
326 | def _bash_syntax_check(self, filename):
327 | self._logger.debug('Checking Bash syntax for file: %s', filename)
328 | return self._run_quiet(self.BIN_BASH, '-n', filename)
329 |
330 | def _gcc_syntax_check(self, filename):
331 | self._logger.debug('Checking C syntax for file: %s', filename)
332 | return self._run_quiet(self.BIN_GCC, '-fsyntax-only', filename)
333 |
334 | def _run_quiet(self, *pargs):
335 | with open('/dev/null', 'w') as dev_null:
336 | return not bool(subprocess.call(pargs, stdin=None, stdout=dev_null,
337 | stderr=dev_null))
338 |
339 | def main(base_path, pkg_desc_file, skip_tarball=False, tarball=None, keep_package_xml=False,
340 | debug=False, skip_syntax_check=False, **kwargs):
341 | pkgr = Magento_Packager(base_path, debug=debug)
342 | pkg_xml = pkgr.build_package_xml(pkg_desc_file)
343 | if not skip_syntax_check:
344 | if not pkgr.do_syntax_check():
345 | raise SystemExit('Syntax check failed!')
346 | if not skip_tarball:
347 | pkgr.build_tarball(pkg_xml, tarball_name=tarball,
348 | keep_pkg_xml=keep_package_xml)
349 |
350 | if __name__ == '__main__':
351 | import sys
352 | import optparse
353 | logging.basicConfig()
354 | parser = optparse.OptionParser()
355 | parser.add_option('-d', '--debug', action='store_true',
356 | default=os.environ.get('MPKG_DEV', False))
357 | parser.add_option('-p', '--keep-package-xml', action='store_true', default=False)
358 | parser.add_option('-t', '--tarball', action='store', default=None)
359 | parser.add_option('-T', '--skip-tarball', action='store_true', default=False)
360 | parser.add_option('-S', '--skip-syntax-check', action='store_true', default=False)
361 | opts, args = parser.parse_args()
362 | base_path = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
363 | if len(args):
364 | main(base_path, args[0], **vars(opts))
365 | else:
366 | print 'Missing package definition file argument (mage-package.xml)!'
367 |
--------------------------------------------------------------------------------
/build/mage-package.xml:
--------------------------------------------------------------------------------
1 | <_>
2 | HE_TwoFactorAuth
3 | community
4 |
5 | 2
6 |
7 | Magento Two Factor Authentication Module
8 | Sentry Two-Factor Authentication will protect your Magento store and customer data by adding an extra check to authenticate your Admin users before allowing them access. Developed as a partnership between the Human Element Magento Development team and Nexcess Hosting, Sentry Two-Factor Authentication for Magento is easy to setup and admin users can quickly login.
9 | GPLv2
10 | http://opensource.org/licenses/GPL-2.0
11 | 0.0.3
12 | stable
13 | Supports Magento v1.6 and later
14 |
15 |
16 | Aric Watson
17 | Gregg Milligan
18 | Greg Croasdill
19 |
20 |
21 | aricwatson
22 | -
23 | -
24 |
25 |
26 | awatson@nexcess.net
27 | -
28 | -
29 |
30 |
31 | 5.2.13
32 | 7.1.0
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | Core
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | magecommunity
66 | magedesign
67 | magedesign
68 | mageetc
69 | magejs
70 | magelib
71 | magelib
72 |
73 |
74 | HE/TwoFactorAuth
75 | adminhtml/default/default/layout/he_twofactor/auth.xml
76 | adminhtml/default/default/template/he_twofactor
77 | modules/HE_TwoFactorAuth.xml
78 | he_twofactor
79 | Duo
80 | GoogleAuthenticator/PHPGangsta
81 |
82 |
83 | dir
84 | file
85 | dir
86 | file
87 | dir
88 | dir
89 | dir
90 |
91 |
92 | ~.*~
93 |
94 | ~.*~
95 |
96 | ~.*~
97 | ~.*~
98 | ~.*~
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/js/he_twofactor/Duo-Web-v1.bundled.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery JavaScript Library v1.4.2
3 | * http://jquery.com/
4 | *
5 | * Copyright 2010, John Resig
6 | * Dual licensed under the MIT or GPL Version 2 licenses.
7 | * http://jquery.org/license
8 | *
9 | * Includes Sizzle.js
10 | * http://sizzlejs.com/
11 | * Copyright 2010, The Dojo Foundation
12 | * Released under the MIT, BSD, and GPL Licenses.
13 | *
14 | * Date: Sat Feb 13 22:33:48 2010 -0500
15 | */
16 | (function(aN,C){var a=function(aZ,a0){return new a.fn.init(aZ,a0)},n=aN.jQuery,S=aN.$,ac=aN.document,Y,Q=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,aX=/^.[^:#\[\.,]*$/,ay=/\S/,N=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,e=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,b=navigator.userAgent,u,L=false,ae=[],aH,au=Object.prototype.toString,aq=Object.prototype.hasOwnProperty,g=Array.prototype.push,G=Array.prototype.slice,s=Array.prototype.indexOf;a.fn=a.prototype={init:function(aZ,a2){var a1,a3,a0,a4;if(!aZ){return this}if(aZ.nodeType){this.context=this[0]=aZ;this.length=1;return this}if(aZ==="body"&&!a2){this.context=ac;this[0]=ac.body;this.selector="body";this.length=1;return this}if(typeof aZ==="string"){a1=Q.exec(aZ);if(a1&&(a1[1]||!a2)){if(a1[1]){a4=(a2?a2.ownerDocument||a2:ac);a0=e.exec(aZ);if(a0){if(a.isPlainObject(a2)){aZ=[ac.createElement(a0[1])];a.fn.attr.call(aZ,a2,true)}else{aZ=[a4.createElement(a0[1])]}}else{a0=K([a1[1]],[a4]);aZ=(a0.cacheable?a0.fragment.cloneNode(true):a0.fragment).childNodes}return a.merge(this,aZ)}else{a3=ac.getElementById(a1[2]);if(a3){if(a3.id!==a1[2]){return Y.find(aZ)}this.length=1;this[0]=a3}this.context=ac;this.selector=aZ;return this}}else{if(!a2&&/^\w+$/.test(aZ)){this.selector=aZ;this.context=ac;aZ=ac.getElementsByTagName(aZ);return a.merge(this,aZ)}else{if(!a2||a2.jquery){return(a2||Y).find(aZ)}else{return a(a2).find(aZ)}}}}else{if(a.isFunction(aZ)){return Y.ready(aZ)}}if(aZ.selector!==C){this.selector=aZ.selector;this.context=aZ.context}return a.makeArray(aZ,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(aZ){return aZ==null?this.toArray():(aZ<0?this.slice(aZ)[0]:this[aZ])},pushStack:function(a0,a2,aZ){var a1=a();if(a.isArray(a0)){g.apply(a1,a0)}else{a.merge(a1,a0)}a1.prevObject=this;a1.context=this.context;if(a2==="find"){a1.selector=this.selector+(this.selector?" ":"")+aZ}else{if(a2){a1.selector=this.selector+"."+a2+"("+aZ+")"}}return a1},each:function(a0,aZ){return a.each(this,a0,aZ)},ready:function(aZ){a.bindReady();if(a.isReady){aZ.call(ac,a)}else{if(ae){ae.push(aZ)}}return this},eq:function(aZ){return aZ===-1?this.slice(aZ):this.slice(aZ,+aZ+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(aZ){return this.pushStack(a.map(this,function(a1,a0){return aZ.call(a1,a0,a1)}))},end:function(){return this.prevObject||a(null)},push:g,sort:[].sort,splice:[].splice};a.fn.init.prototype=a.fn;a.extend=a.fn.extend=function(){var a4=arguments[0]||{},a3=1,a2=arguments.length,a6=false,a7,a1,aZ,a0;if(typeof a4==="boolean"){a6=a4;a4=arguments[1]||{};a3=2}if(typeof a4!=="object"&&!a.isFunction(a4)){a4={}}if(a2===a3){a4=this;--a3}for(;a3