Download - Extending the FreeIPA Server
FreeIPA 3.3 Training SeriesFreeIPA 3.3 Training Series
Extending the FreeIPA Server
Petr Viktorin & Petr Voborník
2014-02-17
2 FreeIPA 3.3 Training Series
Reasons to extend FreeIPA
● Adding a new attribute to an existing object type (e.g. host's OS name, user's employee ID) -- covered in these slides
● Adding a new object type (e.g. DHCP configuration)
● Adding/modifying functionality (e.g. default user login should be lowercased last name only)
● etc.
3 FreeIPA 3.3 Training Series
Adding a custom customer attribute to FreeIPA
● Need to do three steps:● Extending the schema (if attribute is not already there)● Adding an ipalib plugin● Adding a UI plugin
FreeIPA 3.3 Training SeriesFreeIPA 3.3 Training Series
Extending the Schema
5 FreeIPA 3.3 Training Series
Extending the Schema: New attributeType
● Only needed when adding an entirely new attribute (not present in the schema already).
● Register a new OID for the attribute
● Add the new attribute with ldapmodify, e.g.:
dn: cn=schemachangetype: modifyadd: attributeTypesattributeTypes: ( 2.25.28639311321113238241701611583088740684.14.2.2 NAME 'favoriteColorName' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Extending FreeIPA' )
● See RHDS documentation for syntax reference
6 FreeIPA 3.3 Training Series
Extending the Schema: New objectClass
● Only needed when current objectClasses do not already contain the desired attributeType.
● Register a new OID for the objectClass
● Only include new attributes to the MAY clause
● Add the new objectClass with ldapmodify, e.g.:
dn: cn=schemachangetype: modifyadd: objectclassesobjectclasses: ( 2.25.28639311321113238241701611583088740684.14.2.1 NAME 'customPerson' SUP person STRUCTURAL MAY ( favoriteColorName ) X-ORIGIN 'Extending FreeIPA' )
● See RHDS documentation for syntax reference
7 FreeIPA 3.3 Training Series
Schema: Replication and Upgrades
● Schema is replicated to all masters.
● Never modify existing objectClasses, always add new ones.Schema defined by FreeIPA is overwritten on FreeIPA upgrades.
● To update a custom attributeType or objectClass (for example to fix a mistake), add it again with ldapmodify. Make sure to use the same OID and name.
8 FreeIPA 3.3 Training Series
Adding custom objectClass for FreeIPA add commands
● Adding new objectClass for users:ipa config-mod --addattr=ipaUserObjectClasses=customPerson
● Adding new objectClass for groups:ipa config-mod --addattr=ipaGroupObjectClasses=newClass
● For modification commands (and for other object types), a plugin is needed.
FreeIPA 3.3 Training SeriesFreeIPA 3.3 Training Series
Writing a Plugin
10 FreeIPA 3.3 Training Series
Adding an ipalib Plugin
● The UI/CLI does not automatically expose all attributes defined in the schema. Also, custom object classes are not automatically added to new/modified objects. For these tasks a plugin is needed.
● All ipalib plugins are located in /usr/lib/python2.7/site-packages/ipalib/plugins/*.py
● The plugin needs to be present on all FreeIPA servers and on all clients that will use the ipa command.
● After a plugin is added/changed on a server, Apache needs to be reloaded (apachectl graceful).
11 FreeIPA 3.3 Training Series
Exposing an Attribute in CLI
● Example plugin for exposing an attribute:
from ipalib.plugins import userfrom ipalib.parameters import Strfrom ipalib import _
user.user.takes_params = user.user.takes_params + ( Str('favoritecolorname?', cli_name='color', label=_('Favorite color'), ),)
user.user.default_attributes.append('favoritecolorname')
12 FreeIPA 3.3 Training Series
Parameter Names
● The main name is lowercased LDAP attribute name
● The cli_name is used for the CLI option name
● The label is used e.g. in CLI help
from ipalib.plugins import userfrom ipalib.parameters import Intfrom ipalib import _
user.user.takes_params = user.user.takes_params + ( Str('favoritecolorname?', cli_name='color', label=_('Favorite color'), ),)
user.user.default_attributes.append('favoritecolorname')
13 FreeIPA 3.3 Training Series
Specifying Multiple-Valued Attributes
● The tag after the name specifies whether a parameter accepts multiple values, and whether it is required.
● Use “?” (single optional value) or “*” (multiple values)
● Also available: no tag (single required value), and “+” (multiple values, at least one required)
user.user.takes_params = user.user.takes_params + ( Str('favoritecolorname?', cli_name='color', label=_('Favorite color'), ),)
14 FreeIPA 3.3 Training Series
Parameter Types
● Available parameter types: Str, Password, StrEnum, File, DNParam, ...
● See source (ipalib/parameters.py) for details
from ipalib.plugins import userfrom ipalib.parameters import Intfrom ipalib import _
user.user.takes_params = user.user.takes_params + ( Int('employeenumber?', cli_name='number', label=_('Employee number'), ),)
15 FreeIPA 3.3 Training Series
Simple Validation
● Common constraints can be specified in the parameter declaration
● Example – integer range validation:
● See source (ipalib/parameters.py) for others
user.user.takes_params = user.user.takes_params + ( Int('employeenumber?', cli_name='number', label=_('Employee number'), minvalue=1, maxvalue=9999999, ),)
16 FreeIPA 3.3 Training Series
Custom Validation
● A custom validator can be added as a Python function
● Never use ipalib validators for security purposes (values can always be changed directly in LDAP)
$ ipa user-mod johnny --color=magentaipa: ERROR: invalid 'color': magenta is not acceptable
def validate_color(ugettext, value): if value == 'magenta': return _("magenta is not acceptable")
user.user.takes_params = user.user.takes_params + ( Str('favoritecolorname', validate_color, cli_name='color', label=_('Favorite color'), ),)
17 FreeIPA 3.3 Training Series
Adding objectClass in a pre_callback
● In order to add a new objectClass to new and modified objects, we need to add pre_callbacks to the plugin.
def useradd_precallback(self, ldap, dn, entry, attrs_list, *keys, **options): entry['objectclass'].append('customperson') return dn
user.user_add.register_pre_callback(useradd_precallback)
def usermod_precallback(self, ldap, dn, entry, attrs_list, *keys, **options): if 'objectclass' not in entry.keys(): old_entry = ldap.get_entry(dn, ['objectclass']) entry['objectclass'] = old_entry['objectclass'] entry['objectclass'].append('customperson') return dn
user.user_mod.register_pre_callback(usermod_precallback)
18 FreeIPA 3.3 Training Series
Note: --setattr, --addattr, --delattr
● As an alternative to writing a plugin, if the attribute is included in the default objectClasses, we can use the --*attr options in the CLI without modifications to FreeIPA:
● This is a quick lightweight solution for the CLI only.
ipa user-mod --setattr=employeeNumber=123
FreeIPA 3.3 Training SeriesFreeIPA 3.3 Training Series
Extending the Web UI
20 FreeIPA 3.3 Training Series
Web UI
● Has its own plugin system
● Plugins are written in JavaScript
● Plugins can add, remove, change or break the UI● Be careful!
● There is no plugin API atm, sorry.
21 FreeIPA 3.3 Training Series
Plugin name
● has to start with a letter and may contain only ASCII alphanumeric character, underscore _ and dash -
● used as a plugin directory name, AMD package name and main JavaScript file name
22 FreeIPA 3.3 Training Series
Plugin directory
● Plugins are located in `/usr/share/ipa/ui/js/plugins` directory.
● Each plugin has it's own subdirectory with the same name as plugin name
● e.g.: `/usr/share/ipa/ui/js/plugins/employeenumber`
● All plugin files should be in that subdirectory
23 FreeIPA 3.3 Training Series
Main plugin file(module)
● Each plugin has to have a JavaScript file with the same name as plugin name, e.g., employeenumber.js
● This file is entry point of the plugin and is loaded by the plugin system
● It can point to other plugin files or files from core FreeIPA or different plugins. Module system [1] will take care of loading them.
● http://dojotoolkit.org/documentation/tutorials/1.8/modules/
24 FreeIPA 3.3 Training Series
Complete Example – add employee number field
define([ 'freeipa/phases', 'freeipa/user'], function(phases, user_mod) {
// helper functionfunction get_item(array, attr, value) {
for (var i=0,l=array.length; i<l; i++) { if (array[i][attr] === value) return array[i]; } return null;}
var emp_plugin = {};
// adds 'employeenumber' field into user details facetemp_plugin.add_emp_number_pre_op = function() {
var facet = get_item(user_mod.entity_spec.facets, '$type', 'details'); var section = get_item(facet.sections, 'name', 'identity'); section.fields.push({ name: 'employeenumber', label: 'Employee Number' }); return true;};
phases.on('customization', emp_plugin.add_emp_number_pre_op);
return emp_plugin;});
25 FreeIPA 3.3 Training Series
Example result
26 FreeIPA 3.3 Training Series
Example detail – module definion
● Basic structure of a plugin
● Example has two dependencies:● FreeIPA core user module● FreeIPA core phases module
define([ 'freeipa/phases', 'freeipa/user'], function(phases, user_mod) {
var emp_plugin = {};
return emp_plugin;});
27 FreeIPA 3.3 Training Series
UI basics
● Ipalib/LDAP objects are called entities
● Pages are called facets
● Several types of facets, usually related to a type of FreeIPA command● Details – i.e., for output of user-show command● Search – i.e., for user-find● Association – to display member* ● Nested search – special kind of search for nested
objects – i.e., automount keys in automount maps
28 FreeIPA 3.3 Training Series
Specification objects
● UI is semi declarative – most entities, facets, facet content (widgets, field,...) are defined in specification objects
● Specification objects can be obtained from related modules from property named entity_spec or $NAME_spec if the module contains specs for several entities
● Use source code file search to locate particular module. Hint: UI code is in `install/ui/src/freeipa` dir
● You can load the module in browser dev tools, then examine the spec, e.g., by `require('freeipa/user')` call
29 FreeIPA 3.3 Training Series
Back to example
● 'freeipa/user' module was referenced in 'user_mod' variable
● Facets are defined in `facets` array
● We are interested in details facet which can be obtained by searching for object with `$type === 'details'`
var facet = get_item(user_mod.entity_spec.facets, '$type', 'details');
30 FreeIPA 3.3 Training Series
Fields
● Object attributes are defined as fields
● Divided into sections
● Each section has a name
● In the example we wanted to get reference to 'identity' section:
● Fields are defined in `fields` array
● We can just append a new one:
var section = get_item(facet.sections, 'name', 'identity');
section.fields.push({ name: 'employeenumber', label: 'Employee Number'});
31 FreeIPA 3.3 Training Series
Fields
● UI will get field metadata if the field is defined as a server side plugin param
● In such cases the declaraction can be simplified:
● You can specify a $type which controls which widget will be used - like a textarea or multivalued widget
● Note: In complex UIs facet or dialog might not have sections definition and instead it's defined in 'field' and 'widgets' separately with some related linking to each other
section.fields.push('employeenumber');
{ $type: 'multivalued', name: 'mail' }{ $type: 'textarea', name: 'description'}
32 FreeIPA 3.3 Training Series
Hook to application lifecycle
● Web UI is started in several phases controlled by phases module
● Plugins authors who want to add/remove fields should be mostly interested in 'customization' phase – files are loaded, components are not registered nor created, specs objects are available for modification
● Register your own handler:
emp_plugin.add_emp_number_pre_op = function() {
// my plugin code return true; // return some value or a promise in case of async operations};
phases.on('customization', emp_plugin.add_emp_number_pre_op);
33 FreeIPA 3.3 Training Series
Complex plugins
● You can add complete entity pages or something completely different
● Consult source code for more information