Permalink
Browse files

Add: API key support for XML-RPC

  • Loading branch information...
t-kenji committed Mar 17, 2017
1 parent dbe1a7e commit 5fc2cd620d19365fdf8db292fdc47b0ca96f31cd
View
@@ -0,0 +1,4 @@
*.pyc
*.swp
*.egg-info
tags
@@ -420,10 +420,18 @@ msgstr "パスワードが思い出せない?"
msgid "Are you sure you want to delete your account?"
msgstr "本当にアカウントを削除しますか?"
#: acct_mgr/web_ui.py:185
msgid "API-Key is changed, is it OK?"
msgstr "API キーが変更されますが、よろしいですか?"
#: acct_mgr/web_ui.py:162
msgid "Thank you for taking the time to update your password."
msgstr "パスワードの更新に時間を割いてくれてありがとうございます。"
#: acct_mgr/web_ui.py:201
msgid "Refresh your API Key."
msgstr "API キーが再生成されました。"
#: acct_mgr/web_ui.py:171
msgid ""
"You are required to change password because of a recent password change "
@@ -901,6 +909,19 @@ msgstr "アカウントの削除"
msgid "Delete account"
msgstr "アカウントの削除"
#: acct_mgr/templates/prefs_account.html:40
msgid "API Key"
msgstr "API キー"
#: acct_mgr/templates/prefs_account.html:44
msgid "API Key:"
msgstr "API キー:"
#: acct_mgr/templates/prefs_account.html:51
msgid "Refresh API Key"
msgstr "API キーの再生成"
#: acct_mgr/templates/prefs_account.html:53
#: acct_mgr/templates/prefs_account.html:54
msgid "Change Password"
msgstr "パスワードの変更"
@@ -689,10 +689,18 @@ msgstr ""
msgid "Are you sure you want to delete your account?"
msgstr ""
#: acct_mgr/web_ui.py:185
msgid "API-Key is changed, is it OK?"
msgstr ""
#: acct_mgr/web_ui.py:190
msgid "Thank you for taking the time to update your password."
msgstr ""
#: acct_mgr/web_ui.py:201
msgid "Refresh your API Key."
msgstr ""
#: acct_mgr/web_ui.py:199
#, python-format
msgid ""
@@ -1960,6 +1968,18 @@ msgstr ""
msgid "Delete account"
msgstr ""
#: acct_mgr/templates/prefs_account.html:40
msgid "API Key"
msgstr ""
#: acct_mgr/templates/prefs_account.html:44
msgid "API Key:"
msgstr ""
#: acct_mgr/templates/prefs_account.html:51
msgid "Refresh API Key"
msgstr ""
#: acct_mgr/templates/prefs_account.html:53
msgid "Change Password"
msgstr ""
@@ -36,16 +36,46 @@ <h2>Delete Account</h2>
</div>
</form>
</py:if>
<hr />
<h2>API Key</h2>
<form method="post" action="" id="acctmgr_api_key"
onsubmit="return confirm('${refresh_msg_confirm}');">
<div class="field">
<label>API Key:
<div class="apikey truncate autoexpand">${current_apikey}</div>
</label>
</div>
<div class="buttons">
<input type="hidden" name="action" value="refresh" />
<input type="submit"
value="${dgettext('acct_mgr', 'Refresh API Key')}" />
</div>
</form>
</div>
<!--! End insert -->
<head>
<title>Account</title>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('#old_password').focus();
});
</script>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('#old_password').focus();
});
</script>
<style type="text/css">
.apikey {
display: inline-block;
width: 120px;
}
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}
.truncate.autoexpand:hover {
overflow: visible;
}
</style>
</head>
<!--! Commont content, will get wrapped in a form with 'save' button -->
View
@@ -32,7 +32,7 @@
from acct_mgr.api import _, dgettext, ngettext, tag_
from acct_mgr.db import SessionStore
from acct_mgr.guard import AccountGuard
from acct_mgr.model import set_user_attribute
from acct_mgr.model import get_user_attribute, set_user_attribute
from acct_mgr.notification import NotificationError
from acct_mgr.register import RegistrationModule
from acct_mgr.util import if_enabled
@@ -65,6 +65,10 @@ class AccountModule(CommonTemplateProvider):
reset_password = BoolOption(
'account-manager', 'reset_password', True,
'Set to False, if there is no email system setup.')
_apikey_chars = string.ascii_letters + string.digits
apikey_length = IntOption(
'account-manager', 'generated_apikey_length', 40,
"""Length of the generated apikeys created for an account.""")
def __init__(self):
self.acctmgr = AccountManager(self.env)
@@ -176,6 +180,9 @@ def _do_account(self, req):
'delete_enabled': delete_enabled,
'delete_msg_confirm':
_("Are you sure you want to delete your account?"),
'current_apikey': self._get_user_apikey(req.authname),
'refresh_msg_confirm':
_("API-Key is changed, is it OK?"),
}
force_change_password = req.session.get('force_change_passwd', False)
if req.method == 'POST':
@@ -188,6 +195,10 @@ def _do_account(self, req):
force_change_password = False
elif action == 'delete' and delete_enabled:
self._do_delete(req)
elif action == 'refresh':
self._do_refresh_apikey(req)
data['current_apikey'] = self._get_user_apikey(req.authname)
add_notice(req, _("Refresh your API Key."))
if force_change_password:
add_warning(req, tag_(
"You are required to change password because of a recent "
@@ -242,6 +253,13 @@ def _do_delete(self, req):
req.session.save()
req.redirect(req.href.logout())
def _do_refresh_apikey(self, req):
username = req.authname
if not username:
add_warning(req, _("Authentication is required."))
else:
self._refresh_apikey(req, username)
def _do_reset_password(self, req):
email = req.args.get('email')
username = req.args.get('username')
@@ -266,6 +284,23 @@ def _random_password(self):
return ''.join([random.choice(self._password_chars)
for _ in xrange(self.password_length)])
@property
def _generate_apikey(self):
"""
Generate a new apikey on user request.
"""
return ''.join([random.choice(self._apikey_chars)
for _ in xrange(self.apikey_length)])
def _get_user_apikey(self, username):
try:
attrs = get_user_attribute(self.env, username, None, 'apikey')
current_apikey = attrs[username][1].get('apikey')
except:
self.log.info('\'{}\' is API Key not used.'.format(username))
current_apikey = _('Please push "Refresh API Key" button.')
return current_apikey
def _reset_password(self, req, username, email):
"""Store a new, temporary password on admin or user request.
@@ -299,6 +334,12 @@ def _reset_password(self, req, username, email):
if acctmgr.force_passwd_change:
set_user_attribute(self.env, username, 'force_change_passwd', 1)
def _refresh_apikey(self, req, username):
"""
Refresh api-key on user request.
"""
apikey = self._generate_apikey
set_user_attribute(self.env, username, 'apikey', apikey)
class LoginModule(auth.LoginModule, CommonTemplateProvider):
"""Custom login form and processing.
@@ -685,11 +726,19 @@ def _expire_session_cookie(self, req):
def _remote_user(self, req):
"""The real authentication using configured providers and stores."""
username = req.args.get('username')
self.env.log.debug("LoginModule._remote_user: Authentication "
"attempted for '%s'", username)
password = req.args.get('password')
if not username:
apikey = req.environ.get('HTTP_X_TRAC_API_KEY')
if username is not None:
return self._remote_user_by_password(req, username, password)
elif apikey is not None:
return self._remote_user_by_apikey(req, apikey)
else:
return None
def _remote_user_by_password(self, req, username, password):
"""The real authentication using configured providers and stores."""
self.env.log.debug("LoginModule._remote_user: Authentication "
"attempted for '%s'", username)
acctmgr = AccountManager(self.env)
acctmod = AccountModule(self.env)
if acctmod.reset_password_enabled is True:
@@ -722,6 +771,18 @@ def _remote_user(self, req):
return username
return None
def _remote_user_by_apikey(self, req, apikey):
"""The real authentication using API Key."""
username = None
for acct, status in get_user_attribute(self.env,
authenticated=None,
attribute='apikey').iteritems():
if status[1].get('apikey') == apikey:
username = acct
self.env.log.debug("LoginModule._remote_user: Authentication "
"attempted for '%s'", username)
return username
def _format_ctxtnav(self, items):
"""Prepare context navigation items for display on login page."""
return list(separated(items, '|'))

0 comments on commit 5fc2cd6

Please sign in to comment.