mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Add OTP support to ipalib CLI
https://fedorahosted.org/freeipa/ticket/3368
This commit is contained in:
parent
1a9beac1be
commit
397b2876e2
101
API.txt
101
API.txt
@ -524,7 +524,7 @@ option: Int('ipasearchrecordslimit', attribute=True, autofill=False, cli_name='s
|
||||
option: Int('ipasearchtimelimit', attribute=True, autofill=False, cli_name='searchtimelimit', minvalue=-1, multivalue=False, required=False)
|
||||
option: Str('ipaselinuxusermapdefault', attribute=True, autofill=False, cli_name='ipaselinuxusermapdefault', multivalue=False, required=False)
|
||||
option: Str('ipaselinuxusermaporder', attribute=True, autofill=False, cli_name='ipaselinuxusermaporder', multivalue=False, required=False)
|
||||
option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius'))
|
||||
option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius', u'otp'))
|
||||
option: Str('ipauserobjectclasses', attribute=True, autofill=False, cli_name='userobjectclasses', csv=True, multivalue=True, required=False)
|
||||
option: IA5Str('ipausersearchfields', attribute=True, autofill=False, cli_name='usersearch', multivalue=False, required=False)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
|
||||
@ -2219,6 +2219,99 @@ option: Str('version?', exclude='webui')
|
||||
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||
output: Output('value', <type 'unicode'>, None)
|
||||
command: otptoken_add
|
||||
args: 1,20,3
|
||||
arg: Str('ipatokenuniqueid', attribute=True, cli_name='id', multivalue=False, primary_key=True, required=False)
|
||||
option: Str('addattr*', cli_name='addattr', exclude='webui')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
|
||||
option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False)
|
||||
option: Bool('ipatokendisabled', attribute=True, cli_name='disabled', multivalue=False, required=False)
|
||||
option: Str('ipatokenmodel', attribute=True, cli_name='model', multivalue=False, required=False)
|
||||
option: Str('ipatokennotafter', attribute=True, cli_name='not_after', multivalue=False, required=False)
|
||||
option: Str('ipatokennotbefore', attribute=True, cli_name='not_before', multivalue=False, required=False)
|
||||
option: StrEnum('ipatokenotpalgorithm', attribute=True, cli_name='algo', multivalue=False, required=False, values=(u'sha1', u'sha256', u'sha384', u'sha512'))
|
||||
option: IntEnum('ipatokenotpdigits', attribute=True, cli_name='digits', multivalue=False, required=False, values=(6, 8))
|
||||
option: OTPTokenKey('ipatokenotpkey', attribute=True, cli_name='key', multivalue=False, required=False)
|
||||
option: Str('ipatokenowner', attribute=True, cli_name='owner', multivalue=False, required=False)
|
||||
option: Str('ipatokenserial', attribute=True, cli_name='serial', multivalue=False, required=False)
|
||||
option: Int('ipatokentotpclockoffset', attribute=True, cli_name='offset', multivalue=False, required=False)
|
||||
option: Int('ipatokentotptimestep', attribute=True, cli_name='interval', minvalue=5, multivalue=False, required=False)
|
||||
option: Str('ipatokenvendor', attribute=True, cli_name='vendor', multivalue=False, required=False)
|
||||
option: Flag('qrcode?', autofill=True, default=False)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
|
||||
option: Str('setattr*', cli_name='setattr', exclude='webui')
|
||||
option: StrEnum('type', attribute=False, cli_name='type', multivalue=False, required=False, values=(u'totp',))
|
||||
option: Str('version?', exclude='webui')
|
||||
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||
output: Output('value', <type 'unicode'>, None)
|
||||
command: otptoken_del
|
||||
args: 1,2,3
|
||||
arg: Str('ipatokenuniqueid', attribute=True, cli_name='id', multivalue=True, primary_key=True, query=True, required=True)
|
||||
option: Flag('continue', autofill=True, cli_name='continue', default=False)
|
||||
option: Str('version?', exclude='webui')
|
||||
output: Output('result', <type 'dict'>, None)
|
||||
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||
output: Output('value', <type 'unicode'>, None)
|
||||
command: otptoken_find
|
||||
args: 1,20,4
|
||||
arg: Str('criteria?', noextrawhitespace=False)
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
|
||||
option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
|
||||
option: Bool('ipatokendisabled', attribute=True, autofill=False, cli_name='disabled', multivalue=False, query=True, required=False)
|
||||
option: Str('ipatokenmodel', attribute=True, autofill=False, cli_name='model', multivalue=False, query=True, required=False)
|
||||
option: Str('ipatokennotafter', attribute=True, autofill=False, cli_name='not_after', multivalue=False, query=True, required=False)
|
||||
option: Str('ipatokennotbefore', attribute=True, autofill=False, cli_name='not_before', multivalue=False, query=True, required=False)
|
||||
option: StrEnum('ipatokenotpalgorithm', attribute=True, autofill=False, cli_name='algo', multivalue=False, query=True, required=False, values=(u'sha1', u'sha256', u'sha384', u'sha512'))
|
||||
option: IntEnum('ipatokenotpdigits', attribute=True, autofill=False, cli_name='digits', multivalue=False, query=True, required=False, values=(6, 8))
|
||||
option: Str('ipatokenowner', attribute=True, autofill=False, cli_name='owner', multivalue=False, query=True, required=False)
|
||||
option: Str('ipatokenserial', attribute=True, autofill=False, cli_name='serial', multivalue=False, query=True, required=False)
|
||||
option: Int('ipatokentotpclockoffset', attribute=True, autofill=False, cli_name='offset', multivalue=False, query=True, required=False)
|
||||
option: Int('ipatokentotptimestep', attribute=True, autofill=False, cli_name='interval', minvalue=5, multivalue=False, query=True, required=False)
|
||||
option: Str('ipatokenuniqueid', attribute=True, autofill=False, cli_name='id', multivalue=False, primary_key=True, query=True, required=False)
|
||||
option: Str('ipatokenvendor', attribute=True, autofill=False, cli_name='vendor', multivalue=False, query=True, required=False)
|
||||
option: Flag('pkey_only?', autofill=True, default=False)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
|
||||
option: Int('sizelimit?', autofill=False, minvalue=0)
|
||||
option: Int('timelimit?', autofill=False, minvalue=0)
|
||||
option: StrEnum('type', attribute=False, autofill=False, cli_name='type', multivalue=False, query=True, required=False, values=(u'totp',))
|
||||
option: Str('version?', exclude='webui')
|
||||
output: Output('count', <type 'int'>, None)
|
||||
output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
|
||||
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||
output: Output('truncated', <type 'bool'>, None)
|
||||
command: otptoken_mod
|
||||
args: 1,16,3
|
||||
arg: Str('ipatokenuniqueid', attribute=True, cli_name='id', multivalue=False, primary_key=True, query=True, required=True)
|
||||
option: Str('addattr*', cli_name='addattr', exclude='webui')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
|
||||
option: Str('delattr*', cli_name='delattr', exclude='webui')
|
||||
option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False)
|
||||
option: Bool('ipatokendisabled', attribute=True, autofill=False, cli_name='disabled', multivalue=False, required=False)
|
||||
option: Str('ipatokenmodel', attribute=True, autofill=False, cli_name='model', multivalue=False, required=False)
|
||||
option: Str('ipatokennotafter', attribute=True, autofill=False, cli_name='not_after', multivalue=False, required=False)
|
||||
option: Str('ipatokennotbefore', attribute=True, autofill=False, cli_name='not_before', multivalue=False, required=False)
|
||||
option: Str('ipatokenowner', attribute=True, autofill=False, cli_name='owner', multivalue=False, required=False)
|
||||
option: Str('ipatokenserial', attribute=True, autofill=False, cli_name='serial', multivalue=False, required=False)
|
||||
option: Str('ipatokenvendor', attribute=True, autofill=False, cli_name='vendor', multivalue=False, required=False)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
|
||||
option: Str('rename', cli_name='rename', multivalue=False, primary_key=True, required=False)
|
||||
option: Flag('rights', autofill=True, default=False)
|
||||
option: Str('setattr*', cli_name='setattr', exclude='webui')
|
||||
option: Str('version?', exclude='webui')
|
||||
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||
output: Output('value', <type 'unicode'>, None)
|
||||
command: otptoken_show
|
||||
args: 1,4,3
|
||||
arg: Str('ipatokenuniqueid', attribute=True, cli_name='id', multivalue=False, primary_key=True, query=True, required=True)
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
|
||||
option: Flag('rights', autofill=True, default=False)
|
||||
option: Str('version?', exclude='webui')
|
||||
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||
output: Output('value', <type 'unicode'>, None)
|
||||
command: passwd
|
||||
args: 3,1,3
|
||||
arg: Str('principal', autofill=True, cli_name='user', primary_key=True)
|
||||
@ -3707,7 +3800,7 @@ option: Str('initials', attribute=True, autofill=True, cli_name='initials', mult
|
||||
option: Str('ipasshpubkey', attribute=True, cli_name='sshpubkey', csv=True, multivalue=True, required=False)
|
||||
option: Str('ipatokenradiusconfiglink', attribute=True, cli_name='radius', multivalue=False, required=False)
|
||||
option: Str('ipatokenradiususername', attribute=True, cli_name='radius_username', multivalue=False, required=False)
|
||||
option: StrEnum('ipauserauthtype', attribute=True, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius'))
|
||||
option: StrEnum('ipauserauthtype', attribute=True, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius', u'otp'))
|
||||
option: Str('krbprincipalname', attribute=True, autofill=True, cli_name='principal', multivalue=False, required=False)
|
||||
option: Str('l', attribute=True, cli_name='city', multivalue=False, required=False)
|
||||
option: Str('loginshell', attribute=True, cli_name='shell', multivalue=False, required=False)
|
||||
@ -3777,7 +3870,7 @@ option: Str('in_sudorule*', cli_name='in_sudorules', csv=True)
|
||||
option: Str('initials', attribute=True, autofill=False, cli_name='initials', multivalue=False, query=True, required=False)
|
||||
option: Str('ipatokenradiusconfiglink', attribute=True, autofill=False, cli_name='radius', multivalue=False, query=True, required=False)
|
||||
option: Str('ipatokenradiususername', attribute=True, autofill=False, cli_name='radius_username', multivalue=False, query=True, required=False)
|
||||
option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, query=True, required=False, values=(u'password', u'radius'))
|
||||
option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, query=True, required=False, values=(u'password', u'radius', u'otp'))
|
||||
option: Str('krbprincipalname', attribute=True, autofill=False, cli_name='principal', multivalue=False, query=True, required=False)
|
||||
option: Str('l', attribute=True, autofill=False, cli_name='city', multivalue=False, query=True, required=False)
|
||||
option: Str('loginshell', attribute=True, autofill=False, cli_name='shell', multivalue=False, query=True, required=False)
|
||||
@ -3831,7 +3924,7 @@ option: Str('initials', attribute=True, autofill=False, cli_name='initials', mul
|
||||
option: Str('ipasshpubkey', attribute=True, autofill=False, cli_name='sshpubkey', csv=True, multivalue=True, required=False)
|
||||
option: Str('ipatokenradiusconfiglink', attribute=True, autofill=False, cli_name='radius', multivalue=False, required=False)
|
||||
option: Str('ipatokenradiususername', attribute=True, autofill=False, cli_name='radius_username', multivalue=False, required=False)
|
||||
option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius'))
|
||||
option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius', u'otp'))
|
||||
option: Str('l', attribute=True, autofill=False, cli_name='city', multivalue=False, required=False)
|
||||
option: Str('loginshell', attribute=True, autofill=False, cli_name='shell', multivalue=False, required=False)
|
||||
option: Str('mail', attribute=True, autofill=False, cli_name='email', multivalue=True, required=False)
|
||||
|
2
VERSION
2
VERSION
@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000
|
||||
# #
|
||||
########################################################
|
||||
IPA_API_VERSION_MAJOR=2
|
||||
IPA_API_VERSION_MINOR=71
|
||||
IPA_API_VERSION_MINOR=72
|
||||
|
@ -67,6 +67,7 @@ BuildRequires: python-memcached
|
||||
BuildRequires: sssd >= 1.9.2
|
||||
BuildRequires: python-lxml
|
||||
BuildRequires: python-pyasn1 >= 0.0.9a
|
||||
BuildRequires: python-qrcode
|
||||
BuildRequires: python-dns
|
||||
BuildRequires: m2crypto
|
||||
BuildRequires: check
|
||||
@ -130,6 +131,7 @@ Requires: python-ldap
|
||||
Requires: python-krbV
|
||||
Requires: acl
|
||||
Requires: python-pyasn1
|
||||
Requires: python-qrcode
|
||||
Requires: memcached
|
||||
Requires: python-memcached
|
||||
Requires: systemd-units >= 38
|
||||
|
@ -202,7 +202,7 @@ class config(LDAPObject):
|
||||
cli_name='user_auth_type',
|
||||
label=_('Default user authentication types'),
|
||||
doc=_('Default types of supported user authentication'),
|
||||
values=(u'password', u'radius'),
|
||||
values=(u'password', u'radius', u'otp'),
|
||||
csv=True,
|
||||
),
|
||||
)
|
||||
|
329
ipalib/plugins/otptoken.py
Normal file
329
ipalib/plugins/otptoken.py
Normal file
@ -0,0 +1,329 @@
|
||||
# Authors:
|
||||
# Nathaniel McCallum <npmccallum@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ipalib.plugins.baseldap import DN, LDAPObject, LDAPCreate, LDAPDelete, LDAPUpdate, LDAPSearch, LDAPRetrieve
|
||||
from ipalib import api, Int, Str, Bool, Flag, Bytes, IntEnum, StrEnum, _, ngettext
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.errors import PasswordMismatch, ConversionError, LastMemberError, NotFound
|
||||
from ipalib.request import context
|
||||
import base64
|
||||
import uuid
|
||||
import random
|
||||
import urllib
|
||||
import qrcode
|
||||
|
||||
__doc__ = _("""
|
||||
OTP Tokens
|
||||
|
||||
Manage OTP tokens.
|
||||
|
||||
IPA supports the use of OTP tokens for multi-factor authentication. This
|
||||
code enables the management of OTP tokens.
|
||||
|
||||
EXAMPLES:
|
||||
|
||||
Add a new token:
|
||||
ipa otp-add --type=totp --owner=jdoe --desc="My soft token"
|
||||
|
||||
Examine the token:
|
||||
ipa otp-show a93db710-a31a-4639-8647-f15b2c70b78a
|
||||
|
||||
Change the vendor:
|
||||
ipa otp-mod a93db710-a31a-4639-8647-f15b2c70b78a --vendor="Red Hat"
|
||||
|
||||
Delete a token:
|
||||
ipa otp-del a93db710-a31a-4639-8647-f15b2c70b78a
|
||||
""")
|
||||
|
||||
register = Registry()
|
||||
|
||||
TOKEN_TYPES = (u'totp',)
|
||||
|
||||
# NOTE: For maximum compatibility, KEY_LENGTH % 5 == 0
|
||||
KEY_LENGTH = 10
|
||||
|
||||
class OTPTokenKey(Bytes):
|
||||
"""A binary password type specified in base32."""
|
||||
|
||||
password = True
|
||||
|
||||
kwargs = Bytes.kwargs + (
|
||||
('confirm', bool, True),
|
||||
)
|
||||
|
||||
def _convert_scalar(self, value, index=None):
|
||||
if isinstance(value, (tuple, list)) and len(value) == 2:
|
||||
(p1, p2) = value
|
||||
if p1 != p2:
|
||||
raise PasswordMismatch(name=self.name, index=index)
|
||||
value = p1
|
||||
|
||||
if isinstance(value, unicode):
|
||||
try:
|
||||
value = base64.b32decode(value, True)
|
||||
except TypeError, e:
|
||||
raise ConversionError(name=self.name, index=index, error=str(e))
|
||||
|
||||
return Bytes._convert_scalar(value, index)
|
||||
|
||||
def _convert_owner(userobj, entry_attrs, options):
|
||||
if 'ipatokenowner' in entry_attrs and not options.get('raw', False):
|
||||
entry_attrs['ipatokenowner'] = map(userobj.get_primary_key_from_dn,
|
||||
entry_attrs['ipatokenowner'])
|
||||
|
||||
def _normalize_owner(userobj, entry_attrs):
|
||||
owner = entry_attrs.get('ipatokenowner', None)
|
||||
if owner is not None:
|
||||
entry_attrs['ipatokenowner'] = userobj.get_dn(owner)
|
||||
|
||||
|
||||
@register()
|
||||
class otptoken(LDAPObject):
|
||||
"""
|
||||
OTP Token object.
|
||||
"""
|
||||
container_dn = api.env.container_otp
|
||||
object_name = _('OTP tokens')
|
||||
object_name_plural = _('OTP tokens')
|
||||
object_class = ['ipatoken']
|
||||
possible_objectclasses = ['ipatokentotp']
|
||||
default_attributes = [
|
||||
'ipatokenuniqueid', 'description', 'ipatokenowner',
|
||||
'ipatokendisabled', 'ipatokennotbefore', 'ipatokennotafter',
|
||||
'ipatokenvendor', 'ipatokenmodel', 'ipatokenserial'
|
||||
]
|
||||
rdn_is_primary_key = True
|
||||
|
||||
label = _('OTP tokens')
|
||||
label_singular = _('OTP token')
|
||||
|
||||
takes_params = (
|
||||
Str('ipatokenuniqueid',
|
||||
cli_name='id',
|
||||
label=_('Unique ID'),
|
||||
primary_key=True,
|
||||
flags=('optional_create'),
|
||||
),
|
||||
StrEnum('type?',
|
||||
label=_('Type'),
|
||||
values=TOKEN_TYPES,
|
||||
flags=('virtual_attribute', 'no_update'),
|
||||
),
|
||||
Str('description?',
|
||||
cli_name='desc',
|
||||
label=_('Description'),
|
||||
),
|
||||
Str('ipatokenowner?',
|
||||
cli_name='owner',
|
||||
label=_('Owner'),
|
||||
),
|
||||
Bool('ipatokendisabled?',
|
||||
cli_name='disabled',
|
||||
label=_('Disabled state')
|
||||
),
|
||||
Str('ipatokennotbefore?',
|
||||
cli_name='not_before',
|
||||
label=_('Validity start'),
|
||||
),
|
||||
Str('ipatokennotafter?',
|
||||
cli_name='not_after',
|
||||
label=_('Validity end'),
|
||||
),
|
||||
Str('ipatokenvendor?',
|
||||
cli_name='vendor',
|
||||
label=_('Vendor'),
|
||||
),
|
||||
Str('ipatokenmodel?',
|
||||
cli_name='model',
|
||||
label=_('Model'),
|
||||
),
|
||||
Str('ipatokenserial?',
|
||||
cli_name='serial',
|
||||
label=_('Serial'),
|
||||
),
|
||||
OTPTokenKey('ipatokenotpkey?',
|
||||
cli_name='key',
|
||||
label=_('Key'),
|
||||
flags=('no_display', 'no_update', 'no_search'),
|
||||
),
|
||||
StrEnum('ipatokenotpalgorithm?',
|
||||
cli_name='algo',
|
||||
label=_('Algorithm'),
|
||||
flags=('no_update'),
|
||||
values=(u'sha1', u'sha256', u'sha384', u'sha512'),
|
||||
),
|
||||
IntEnum('ipatokenotpdigits?',
|
||||
cli_name='digits',
|
||||
label=_('Display length'),
|
||||
values=(6, 8),
|
||||
flags=('no_update'),
|
||||
),
|
||||
Int('ipatokentotpclockoffset?',
|
||||
cli_name='offset',
|
||||
label=_('Clock offset'),
|
||||
flags=('no_update'),
|
||||
),
|
||||
Int('ipatokentotptimestep?',
|
||||
cli_name='interval',
|
||||
label=_('Clock interval'),
|
||||
minvalue=5,
|
||||
flags=('no_update'),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@register()
|
||||
class otptoken_add(LDAPCreate):
|
||||
__doc__ = _('Add a new OTP token.')
|
||||
msg_summary = _('Added OTP token "%(value)s"')
|
||||
|
||||
takes_options = LDAPCreate.takes_options + (
|
||||
Flag('qrcode?', label=_('Display QR code (requires wide terminal)')),
|
||||
)
|
||||
|
||||
has_output_params = LDAPCreate.has_output_params + (
|
||||
Str('uri?', label=_('URI')),
|
||||
)
|
||||
|
||||
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
|
||||
# Set defaults. This needs to happen on the server side because we may
|
||||
# have global configurable defaults in the near future.
|
||||
options.setdefault('type', TOKEN_TYPES[0])
|
||||
if entry_attrs.get('ipatokenuniqueid', None) is None:
|
||||
entry_attrs['ipatokenuniqueid'] = str(uuid.uuid4())
|
||||
dn = DN("ipatokenuniqueid=%s" % entry_attrs['ipatokenuniqueid'], dn)
|
||||
entry_attrs.setdefault('ipatokenvendor', u'FreeIPA')
|
||||
entry_attrs.setdefault('ipatokenmodel', options['type'])
|
||||
entry_attrs.setdefault('ipatokenserial', entry_attrs['ipatokenuniqueid'])
|
||||
entry_attrs.setdefault('ipatokenotpalgorithm', u'sha1')
|
||||
entry_attrs.setdefault('ipatokenotpdigits', 6)
|
||||
entry_attrs.setdefault('ipatokentotpclockoffset', 0)
|
||||
entry_attrs.setdefault('ipatokentotptimestep', 30)
|
||||
entry_attrs.setdefault('ipatokenotpkey',
|
||||
"".join(map(chr, random.SystemRandom().sample(range(255), KEY_LENGTH))))
|
||||
|
||||
# Set the object class
|
||||
if options['type'] == 'totp':
|
||||
entry_attrs['objectclass'] = otptoken.object_class + ['ipatokentotp']
|
||||
|
||||
# Resolve the user's dn
|
||||
_normalize_owner(self.api.Object.user, entry_attrs)
|
||||
|
||||
# Get the issuer for the URI
|
||||
owner = entry_attrs.get('ipatokenowner', None)
|
||||
issuer = api.env.realm
|
||||
if owner is not None:
|
||||
try:
|
||||
issuer = ldap.get_entry(owner, ['krbprincipalname'])['krbprincipalname'][0]
|
||||
except (NotFound, IndexError):
|
||||
pass
|
||||
|
||||
# Build the URI parameters
|
||||
args = {}
|
||||
args['issuer'] = issuer
|
||||
args['secret'] = base64.b32encode(entry_attrs['ipatokenotpkey'])
|
||||
args['digits'] = entry_attrs['ipatokenotpdigits']
|
||||
args['period'] = entry_attrs['ipatokentotptimestep']
|
||||
args['algorithm'] = entry_attrs['ipatokenotpalgorithm']
|
||||
|
||||
# Build the URI
|
||||
label = urllib.quote(entry_attrs['ipatokenuniqueid'])
|
||||
parameters = urllib.urlencode(args)
|
||||
uri = u'otpauth://totp/%s:%s?%s' % (issuer, label, parameters)
|
||||
setattr(context, 'uri', uri)
|
||||
|
||||
return dn
|
||||
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
entry_attrs['uri'] = getattr(context, 'uri')
|
||||
_convert_owner(self.api.Object.user, entry_attrs, options)
|
||||
return super(otptoken_add, self).post_callback(ldap, dn, entry_attrs, *keys, **options)
|
||||
|
||||
def output_for_cli(self, textui, output, *args, **options):
|
||||
uri = output['result'].get('uri', None)
|
||||
rv = super(otptoken_add, self).output_for_cli(textui, output, *args, **options)
|
||||
|
||||
# Print QR code to terminal if specified
|
||||
if uri and options.get('qrcode', False):
|
||||
print "\n"
|
||||
qr = qrcode.QRCode()
|
||||
qr.add_data(uri)
|
||||
qr.make()
|
||||
qr.print_tty()
|
||||
print "\n"
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
@register()
|
||||
class otptoken_del(LDAPDelete):
|
||||
__doc__ = _('Delete an OTP token.')
|
||||
msg_summary = _('Deleted OTP token "%(value)s"')
|
||||
|
||||
|
||||
@register()
|
||||
class otptoken_mod(LDAPUpdate):
|
||||
__doc__ = _('Modify a OTP token.')
|
||||
msg_summary = _('Modified OTP token "%(value)s"')
|
||||
|
||||
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
|
||||
_normalize_owner(self.api.Object.user, entry_attrs)
|
||||
return dn
|
||||
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
_convert_owner(self.api.Object.user, entry_attrs, options)
|
||||
return super(otptoken_mod, self).post_callback(ldap, dn, entry_attrs, *keys, **options)
|
||||
|
||||
|
||||
@register()
|
||||
class otptoken_find(LDAPSearch):
|
||||
__doc__ = _('Search for OTP token.')
|
||||
msg_summary = ngettext(
|
||||
'%(count)d OTP token matched', '%(count)d OTP tokens matched', 0
|
||||
)
|
||||
|
||||
def pre_callback(self, ldap, filters, *args, **kwargs):
|
||||
# This is a hack, but there is no other way to
|
||||
# replace the objectClass when searching
|
||||
type = kwargs.get('type', '')
|
||||
if type not in TOKEN_TYPES:
|
||||
type = ''
|
||||
filters = filters.replace("(objectclass=ipatoken)",
|
||||
"(objectclass=ipatoken%s)" % type)
|
||||
|
||||
return super(otptoken_find, self).pre_callback(ldap, filters, *args, **kwargs)
|
||||
|
||||
def args_options_2_entry(self, *args, **options):
|
||||
entry = super(otptoken_find, self).args_options_2_entry(*args, **options)
|
||||
_normalize_owner(self.api.Object.user, entry)
|
||||
return entry
|
||||
|
||||
def post_callback(self, ldap, entries, truncated, *args, **options):
|
||||
for entry in entries:
|
||||
_convert_owner(self.api.Object.user, entry, options)
|
||||
return super(otptoken_find, self).post_callback(ldap, entries, truncated, *args, **options)
|
||||
|
||||
|
||||
@register()
|
||||
class otptoken_show(LDAPRetrieve):
|
||||
__doc__ = _('Display information about an OTP token.')
|
||||
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
_convert_owner(self.api.Object.user, entry_attrs, options)
|
||||
return super(otptoken_show, self).post_callback(ldap, dn, entry_attrs, *keys, **options)
|
@ -379,7 +379,7 @@ class user(LDAPObject):
|
||||
cli_name='user_auth_type',
|
||||
label=_('User authentication types'),
|
||||
doc=_('Types of supported user authentication'),
|
||||
values=(u'password', u'radius'),
|
||||
values=(u'password', u'radius', u'otp'),
|
||||
csv=True,
|
||||
),
|
||||
Str('userclass*',
|
||||
@ -648,6 +648,14 @@ class user_del(LDAPDelete):
|
||||
def pre_callback(self, ldap, dn, *keys, **options):
|
||||
assert isinstance(dn, DN)
|
||||
check_protected_member(keys[-1])
|
||||
|
||||
# Delete all tokens owned by this user
|
||||
owner = self.api.Object.user.get_primary_key_from_dn(dn)
|
||||
results = self.api.Command.otptoken_find(ipatokenowner=owner)['result']
|
||||
for token in results:
|
||||
token = self.api.Object.otptoken.get_primary_key_from_dn(token['dn'])
|
||||
self.api.Command.otptoken_del(token)
|
||||
|
||||
return dn
|
||||
|
||||
api.register(user_del)
|
||||
|
Loading…
Reference in New Issue
Block a user