diff options
Diffstat (limited to 'doc/guide')
-rw-r--r-- | doc/guide/Makefile | 36 | ||||
-rw-r--r-- | doc/guide/README | 36 | ||||
-rw-r--r-- | doc/guide/guide.org | 1060 | ||||
-rw-r--r-- | doc/guide/netgroup.js | 62 | ||||
-rw-r--r-- | doc/guide/role.py | 140 | ||||
-rw-r--r-- | doc/guide/wsgi.py | 26 |
6 files changed, 1360 insertions, 0 deletions
diff --git a/doc/guide/Makefile b/doc/guide/Makefile new file mode 100644 index 000000000..2fc4af48b --- /dev/null +++ b/doc/guide/Makefile @@ -0,0 +1,36 @@ +FILE=guide.org +XML=$(addsuffix .xml, $(basename $(FILE))) +PDF=$(addsuffix .pdf, $(basename $(FILE))) +TXT=$(addsuffix .txt, $(basename $(FILE))) +HTML=$(addsuffix .html, $(basename $(FILE))) +FO=$(addsuffix .fo, $(basename $(FILE))) + +all: $(PDF) $(TXT) $(HTML) + @echo Finished: $? are created + +plain: $(FILE) + @echo -n "Building HTML, Docbook, and plain text ..." + @emacs -batch -q --no-site-file -eval "(require 'org)" \ + --visit $< -f org-export-as-html \ + --visit $< -f org-export-as-docbook \ + --visit $< -f org-export-as-ascii 2>/dev/null + @echo "done, see $(HTML), $(XML), $(TXT)" + +$(TXT): plain + +$(HTML): plain + +$(XML): plain + +$(FO): $(XML) + @xmlto --skip-validation fo $< 2>/dev/null + +$(PDF): $(FO) + @echo -n "Building PDF ... " + @fop -fo $< -pdf $@ -l en -a 2>/dev/null + @echo "done, see $(PDF)" + +.PHONY: clean + +clean: + @rm -f *.html *.txt *.xml *.fo *.pdf *~ diff --git a/doc/guide/README b/doc/guide/README new file mode 100644 index 000000000..2bff131be --- /dev/null +++ b/doc/guide/README @@ -0,0 +1,36 @@ +Extending FreeIPA +----------------- + +"Extending FreeIPA" is a developer guide to understand how FreeIPA core +framework is built and how to extend it. + +The Guide is written using Emacs Org Mode, see http://orgmode.org/org.html +for extensive manual of supported markup features. + +You don't need to use Emacs to edit it, the markup is a plain text. + +Building the guide +------------------ + +There is Makefile which can be used to convert the Guide from +Emacs Org Mode format to different targets. + +Prerequisites: +============== +On Fedora system following packages are required to generate The Guide: + +docbook-style-xsl +fop +emacs +xmlto + +HTML, Docbook, and plain text +--- +As Org Mode is part of Emacs since version 22, building HTML, TXT, and +Docbook targets requires Emacs v22 and above (tested with v23.3 in Fedora). + +PDF +--- +Building PDF is done first generating Docbook source, converting it to FO format, +and then running 'fop' processor. + diff --git a/doc/guide/guide.org b/doc/guide/guide.org new file mode 100644 index 000000000..5fffdab29 --- /dev/null +++ b/doc/guide/guide.org @@ -0,0 +1,1060 @@ +#+OPTIONS: ^:{} +#+EMAIL: abokovoy@redhat.com +#+AUTHOR: Alexander Bokovoy +#+STYLE: <style type="text/css"> +#+STYLE: pre { +#+STYLE: border: 1pt solid #000000; +#+STYLE: background-color: #404040; +#+STYLE: color: white; +#+STYLE: } +#+STYLE: .src {width: 940px;} +#+STYLE: dt {width: 400px; margin 25px auto;} +#+STYLE: dd {width: 940px;} +#+STYLE: p {text-align:justify;} +#+STYLE: body {width: 960px; +#+STYLE: margin: 0 auto; +#+STYLE: overflow: hidden;} +#+STYLE: div#content {margin: 0 10px 0 10px; +#+STYLE: display: inline; +#+STYLE: float: left; +#+STYLE: width: 940px; +#+STYLE: overflow: hidden;} +#+STYLE: </style> +Extending FreeIPA +* Introduction +FreeIPA is an integrated security information management solution. There is a common +framework written in Python to command LDAP server provided by a 389-ds project, certificate +services of a Dogtag project, and a MIT Kerberos server, as well as configuring various other +services typically used to maintain integrity of an enterprise environment, like DNS and +time management (NTP). The framework is written in Python, runs at a server side, and +provides access via command line tools or web-based user interface. + +As core parts of the framework are implemented as pluggable modules, it is possible to +extend FreeIPA on multiple levels. This document attempts to present general ideas and +ways to make use of most of extensibility points in FreeIPA. + +For information management solutions extensibility could mean multiple things. Information +objects that are managed could be extended themselves or new objects could be added. New +operations on existing objects might become needed or certain aspects of an object should +be hidden in a specific environment. All these tasks may require quite different approaches +to implement. + +Following chapters will cover high-level design of FreeIPA and dive into details of its core +framework. Knowledge of Python programming language basics is required. Understanding +LDAP concepts is desirable, though it is not required for simple +extensions as FreeIPA attempts to provide sufficient mapping of LDAP concepts onto less +complex structures and Python objects, lowering a barrier to fine tune FreeIPA for +the specific use cases. +* High level design +FreeIPA core is written in Python programming language. The data is stored in LDAP +database, and client-server paradigm is used for managing it. A FreeIPA server instance +runs its own LDAP database, provided by 389-ds project (formerly Fedora Directory +Server). A single instance of LDAP database corresponds to the single FreeIPA +domain. Access to all information stored in the database is provided via FreeIPA server +core which is run as a simple WSGI application which uses XML-RPC and JSON to exchange +requests with its own clients. + +Multiple replicas of the FreeIPA instance can be created on different servers, they are +managed with the help of replication mechanisms of 389-ds directory server. + +As LDAP database is used for data storage, LDAP's Access Control Model is used to provide +privilege separation and Kerberos tickets are used to pass-through assertion of +authenticity. As Kerberos server is using the same LDAP database instance, use of Kerberos +tickets allows to perform operations against the database on the server if a client is +capable to forward such tickets via communication channels selected for the operation. + +When FreeIPA client connects to FreeIPA server, a Kerberos ticket is forwarded +to the server and operations against LDAP database are performed under identity +authenticated when the ticket was issued. As LDAP database also uses Kerberos to establish +identity of a client, Access Control Information attributes can be used to limit what +entries could be accessed and what operations could be performed. + +The approach allows to delegate operations from a FreeIPA client to the FreeIPA server +and in general gives FreeIPA server ability to interact with any Kerberos-aware service on +behalf of the client. It also allows to keep FreeIPA client side implementation relatively +light-weight: all it needs to do is to be able to forward Kerberos ticket, process XML-RPC or +JSON, and present resulting responses to the user. + +Besides run-time core, FreeIPA includes few configuration tools. These tools +are split between server and client. Server-side tools are used when an instance of +FreeIPA server is set up and configured, while client-side tools are used to configure client +systems. While the server tools are used to configure LDAP database, put proper schema +definitions in use, create Kerberos domain, Certificate Authority and configure all +corresponding services, client side is more limited to configure PAM/NSS modules to work +against FreeIPA server, and make sure that appropriate information about the client host +is recorded in FreeIPA databases. +* Core plug-in framework +FreeIPA core defines few fundamentals. These are managed objects, their properties, and +methods to apply actions to the objects. Methods, in turn, are commands that are +associated with a specific object. Additionally, there are commands that do not have +directly associated objects and may perform actions over few of those. Objects are stored +using data store represented by a back end, and one of most useful back ends is LDAP store +back end. + +Altogether, set of =Object=, =Property=, =Method=, =Command=, and =Backend= instances +represent application programming interface, API, of FreeIPA core framework. + +In Python programming language object oriented support is implemented using a fairly +simple concept that allows to modify instances in place, extending or removing their +properties and methods. While this concept is highly useful, in security-oriented +frameworks ability to lock down and trace origins of changes is also important. FreeIPA core +attempts to implement locking down feature by artificially making instances of foundation +classes read-only after their initialization has happened. If an attempt to modify object +happens after it was locked down, an exception is thrown. There are many classes +following this pattern. + +For example, =ipalib.frontend.Command= class is derived from =ipalib.frontend.HasParam= class +that derives from =ipalib.plugable.Plugin= class which, in turn, is derived from +=ipalib.base.ReadOnly= class. + +As result, every command has typed parameters and can dynamically be added to the +framework. At the same time, one cannot modify the properties of the command accidentally +once it is instantiated. This protects from modifications and enforces true nature of the +commands: they cannot have state that is carried over across multiple calls to the same +command unless the state is changing globally the whole environment around. + +Environment also holds information about the context of execution. The /context/ is +important part of the FreeIPA framework as it also defines which methods of +the command instance are called in order to perform action. /Context/ in itself is defined +by the /environment/ which gives means to catch and store certain information about execution. +As with commands themselves, once instantiated, environment cannot be changed. + +By default, for primary FreeIPA use, there are three major contexts defined: server, +client, and installer/updates. + +- /server context/ :: plugins are registered and communicate with clients via XML-RPC and JSON + listeners. They validate any arguments and options defined and then execute whatever + action they supposed to perform +- /client context/ :: plugins are used to validate any arguments and options they take and + then forward the request to the FreeIPA server. +- /installer context/, /updates context/ :: plugins specific to installation and update + are loaded and registered. This context can be used to extend possible operations + during set up of FreeIPA server. + +A user may define any context they want. FreeIPA names server context as '~server~'. When +using the ~ipa~ command line tool the context is '~cli~'. Server installation tools, in +particular, '~ipa-ldap-updater~', use special '~updates~' context to load specialized +plugins useful during update of the installed FreeIPA server. + +Because these utilities use the same framework they will do the same validation, set default +values, and perform other basic actions in all contexts. This can help to save a +round-trip when testing for invalid data. However, for client-server communication, the +server is always authoritative and can re-define what the client has sent. + +** Name space +FreeIPA has one special type of read-only objects: =NameSpace=. =NameSpace= class gives an +ordered, immutable mapping object whose values can also be accessed as attributes. A +=NameSpace= instance is constructed from iterable providing its members, which are simply +arbitrary objects with =name= attribute. This attribute must conform to two following +rules: +- Its value must be unique among the members of the name space +- Its value must pass the =check_name()= function =ipalib.base= module. + +=check_name()= function encodes a simple rule of a lower-case Python identifier that +neither starts nor ends with an underscore. Actual regular expression that codifies this +rule is =NAME_REGEX= within =ipalib.constants= module. + +Once name space is created, it locks itself down and becomes read-only. It means that +while original objects accessed through the name space might change, the references to +them via name space will stay intact. They cannot be removed or changed to point to other +objects. + +The name spaces are used widely in FreeIPA core framework. As mentioned earlier, API +includes set of objects, commands, and methods. Objects include properties that are +defined before lock-down. At object's lock-down parameters are placed into a name space +and that locks them down so that no parameter specification can change. Command's +parameters and options also locked down and cannot change once command instance is +instantiated. + +** Parameters +=Param= class is used to define attributes, arguments, or options throughout FreeIPA core +framework. The =Param= base class is not used directly but rather sub-classed to define +properties like passwords or specific data types like =Str= or =Int=. + +Instances of classes inherited from =Param= base class give uniform access to the +properties required to command line interface, Web UI, and internally to FreeIPA +code. Following properties are most important: + - /name/ :: name of the parameter used internally to address the parameter in Python + code. The /name/ could include special characters to designate a =Param= spec. + - /cli_name/ :: optional name of the parameter to use in command line + interface. FreeIPA's CLI sets a mechanism to automatically translate + from a command line option name to a parameter's /name/ if /cli_name/ + is specified. + - /label/ :: A short phrase describing the parameter. It is used on the CLI when + interactively prompting for the values, and as a label for the form inputs + in the Web UI. The /label/ should start with an initial capital letter. + - /doc/ :: A long description of the parameter. It is used by the CLI when displaying the + help information for a command, and as an extra instruction for the form input + on the Web UI. By default the /doc/ is the same as the /label/ but can be + overridden when a =Param= instance is created. As with /label/, /doc/ should + start with an initial capital letter and additionally should not end with any + punctuation. + - /required/ :: If set to =True=, means this parameter is required to supply. All + parameters are required by default and that means that /required/ + property should only be specified when parameter *is not required*. + - /multivalue/ :: if set to =True=, means this parameter can accept a Python's tuple of + values. By default all parameters are *single-valued*. + +When parameter /name/ has any of ~?~, ~*~, or ~+~ characters, it is treated as parameter +spec and is used to specify whether parameter is required, and should it be +multivalued. Following syntax is used: + +| Spec | Name | Required | Multivalue | +|--------+-------+----------+------------| +| 'var' | 'var' | True | False | +| 'var?' | 'var' | False | False | +| 'var*' | 'var' | False | True | +| 'var+' | 'var' | True | True | + +Access to the value stored by the =Param= class is given through a callable interface: + +#+BEGIN_SRC python +age = Int('age', label='Age', default=100) +print age(10) +#+END_SRC + +Following parameter classes are defined and used throughout FreeIPA framework: +- /Bool/ :: boolean parameters that are stored in Python's ~bool~ type, therefore, they + return either ~True~ or ~False~ value. However, they accept ~1~, ~True~ + (Python boolean), or Unicode strings '~1~', '~true~' and '~TRUE~' as truth value, and ~0~, + ~False~ (Python boolean), or Unicode strings '~0~', '~false~', and '~FALSE~' as false. +- /Flag/ :: boolean parameters which always have default value. Property /default/ can be + used to set the value. Defaults to ~False~: +#+BEGIN_SRC python +verbose = Flag('verbose', default=True) +#+END_SRC +- /Int/ :: integer parameters that are stored in Python's int type. Two additional properties can be + specified when constructing =Int= parameter: + - /minvalue/ :: minimal value that this parameter accepts, defaults to =MININT= + - /maxvalue/ :: maximum value this parameter can accept, defaults to =MAXINT= +- /Float/ :: floating point parameters that are stored in Python's float type. =Float= has + the same two additional properties as =Int=. Unlike =Int=, there are no + default values for the minimal and maximum boundaries. +- /Bytes/ :: a parameter to represent binary data. +- /Str/ :: parameter representing a Unicode text. Both /Bytes/ and /Str/ parameters accept + following additional properties: + - /minlength/ :: minimal length of the parameter + - /maxlength/ :: maximum length of the parameter + - /length/ :: length of the parameters + - /pattern/ :: regular expression applied to the parameter's value to check its + validness + - /pattern_errmsg/ :: an error message to show when regular expression check fails +- /IA5Str/ :: string parameter as defined by RFC 4517. It means all characters of the + string must be ASCII characters (7-bit). +- /Password/ :: parameter to store passwords in Python =unicode= type. /Password/ has one + additional property: + - /confirm/ :: boolean specifying whether password should be confirmed + when entered. The confirmation is enabled by default. +- /Enum/ :: parameter can have one of predefined values that are specified with /values/ + property which is a Python's =tuple=. + +For most common case of enumerable strings there are two parameters: +- /BytesEnum/ :: parameter value should be one of predefined =unicode= strings +- /StrEnum/ :: equivalent to /BytesEnum/. Originally /BytesEnum/ was stored in Python's + =str= class instances but to be aligned with Python 3.0 changes both + classes moved to store as =unicode=. + +When more than one value should be accepted, there is /List/ parameter that allows to +provide list of strings separated by a separator, default to ','. Also, the /List/ +parameter skips spaces before the next item in the list unless property /skipspace/ is set to False: +#+BEGIN_SRC python +names = List('names', separator=',', skipspace=True) +names_list = names(u'John Doe, John Lee, Brad Moe') +# names_list is (u'John Doe', u'John Lee', u'Brad Moe') +names = List('names', separator=',', skipspace=False) +names_list = names(u'John Doe, John Lee, Brad Moe') +# names_list is (u'John Doe', u' John Lee', u' Brad Moe') +#+END_SRC + +** Objects +The data manipulated by FreeIPA is represented by an Object class instances. Instance of +an Object class is a collection of properties, accepted parameters, action methods, and a +reference to where this object's data is preserved. Each object also has a reference to a +property that represents a primary key for retrieving the object. + +In addition to properties and parameters, Object class instances hold their labels to use +in user interfaces. In practice, there are few differences in how labels are presented +depending on whether it is command line interface or a Web UI, but they can be ignored at +this point. + +To be useful, all Object sub-classes need to override =takes_param= property. This is +where most of flexibility of FreeIPA comes from. + +*** takes_param attribute +Properties of every object derived from Object class can be specified manually but FreeIPA +gives a handy mechanism to perform descriptive specification. Each =Object= class has +=Object.takes_param= attribute which defines a specification of all parameters this object +type is accepting. + +Next example shows how to create new object type. We create an aquarium tank by defining +its dimensions and specifying which fish is living there. +#+BEGIN_SRC python -n -r -l '(%s)' +from ipalib import api, Object +class tank(Object): + takes_params = ( + StrEnum('species*', label=u'Species', doc=u'Fish species', + values=(u'Angelfish', u'Betta', u'Cichlid', u'Firemouth')), + Float('height', label=u'Height', doc=u'height in mm', default=400.0), + Float('width', label=u'Width', doc=u'width in mm', default=400.0), + Float('depth', label=u'Depth', doc=u'Depth in mm', default=300.0) + ) + +api.register(tank) (ref:register) +api.finalize() (ref:finalize) +print list(api.Object.tank.params) +# ['species', 'height', 'width', 'depth'] +#+END_SRC + +First we define new class, =tank=, that takes four parameters. On line [[(register)]] we register the class +in FreeIPA's API instance, api. This creates =tank= object in =api.Object= name +space. Many objects can be added into the API up until =api.finalize()= is called as we do +on line [[(finalize)]]. + +When =api.finalize()= is called, all name spaces are locked down and all registered Python +objects in those name spaces are also finalized which in turn locks their structure down +as well. + +As result, once we have finalized our API instance, every registered Object can be +accessed through =api.Object.<name>=. Our aquarium tank object now has defined =params= +attribute which is a name space holding all =Param= instances. Thus we can introspect and +see which parameters this object has. + +At this point we can't do anything reasonable with our aquarium tank yet because we +haven't defined methods to handle it. In addition, our object isn't very useful as it does +not know how to store the information about aquarium's dimensions and species living in +it. + +*** Object methods +Methods perform actions on the associated objects. The association of methods and objects +is done through naming convention rather than using programming language features. FreeIPA +expects methods operating on an object =<name>= to be named =<name>_<action>=: +#+BEGIN_SRC python +class tank_create(Method): + def execute(self, **options): + # create new aquarium tank + +api.register(tank_create) + +class tank_populate(Method): + def execute(self, **options): + # populate the aquarium tank with fish + +api.register(tank_populate) +#+END_SRC + +As can be seen, each method is a separate Python class. This approach allows to maintain +complexity of methods isolated from each other and from the complexity of the objects and +their storage which is probably most important aspect due to LDAP complexity overall. + +The linking between objects and their methods goes further. All parameters defined for an +object, may be used as arguments of the methods without explicit declaration. This means +=api.Method.tank_populate= will accept ~species~ argument. + +*** Methods with storage back ends +In order to store the information, =Object= class instances require a back end. FreeIPA +defines several back ends but the ones that could store data are derived of +=ipalib.CrudBackend=. CRUD, or /Create/, /Retrieve/, /Update/, and /Delete/, are basic +operations that could be performed with corresponding objects. =ipalib.crud.CrudBackend= +is an abstract class, it only defines functions that should be overridden in classes that +actually implement the back end operations. + +As back end is not used directly, FreeIPA defines methods that could use back end and +operate on object's defined by certain criteria. Each method is defined as a separate +Python class. As CRUD acronym suggests, there are four base operations: +=ipalib.crud.Create=, =ipalib.crud.Retrieve=, =ipalib.crud.Update=, +=ipalib.crud.Delete=. In addition, method =ipalib.crud.Search= allows to retrieve all +entries that match a given search criteria. + +When objects are defined and the back end is known, methods can be used to manipulate +information stored by the back end. Most of useful operations combine some of CRUD base +operations to perform their tasks. + +In order to support flexible way to extend methods, FreeIPA gives special treatment for +the LDAP back end. Methods using LDAP back end hide complexity of handling LDAP queries and +allow to register user-provided functions that are called before or after method. This +mechanism is defined by ipalib.plugins.baseldap.CallbackInterface and used by LDAP-aware +CRUD classes, =LDAPCreate=, =LDAPRetrieve=, =LDAPUpdate=, =LDAPDelete=, and an analogue to +=ipalib.crud.Search=, =LDAPSearch=. There are also classes that define methods to operate +on reverse relationships between objects in LDAP to allow addition or removal of +membership information both in forward and reverse directions: =LDAPAddMember=, +=LDAPModMember=, =LDAPRemoveMember=, =LDAPAddReverseMember=, =LDAPModReverseMember=, =LDAPRemoveReverseMember=. + +Most of CRUD classes are based on a =LDAPQuery= class which generalizes concept of +querying a record addressed with a primary key and supports JSON marshalling of the +queried attributes and their values. + +Base LDAP operation classes implement everything needed to create typical methods to +work with self-contained objects stored in LDAP. + +*** LDAPObject class +A large class of objects is LDAPObject. LDAPObject instances represent entries stored in +FreeIPA LDAP database instance. They are referenced by their distinguished name, DN, and +able to represent complex relationships between entries in LDAP like direct and indirect +membership. + +Any class derived from LDAPObject needs to re-define few properties so that base class can +properly function for the specific object that is defined by the class. Below are commonly +redefined properties: + - /container_dn/ :: DN of the container for this object entries in LDAP. This one + usually comes from the environment associated with the API and by default is populated + from the =DEFAULT_CONFIG= of =ipalibs.constants=. For example, all accounts are + stored under =cn=accounts=, with users are under =cn=users,cn=accounts= and groups + are under =cn=groups,cn=accounts=. In case of a new object added, it + is reasonable to select its container coordinated to default configuration. + - /object_class/ :: list of LDAP object classes associated with the object + - /search_attributes/ :: list of attributes that will be used for search + - /default_attributes/ :: list of attributes that are always returned by searches + - /uuid_attribute/ :: an attribute that defines uniqueness of the entry + - /attribute_members/ :: a dict defining relations between other objects and this + one. Key is the name of attribute and value is a list of objects this attribute may + refer to. For example, =host= object defines that =memberof= attribute of a + host may refer to a =hostgroup=, =netgroup=, =role=, =hbacrule=, or =sudorule= + object. In other words, it means that =host= could be a member of any of those + objects. + - /reverse_members/ :: a dict defining reverse relations between this object and other + objects. Key is the name of attribute and value is the name of an object that refers + to this object with the attribute. For example, =role= object defines that =member= + attribute of a =privilege= refers to a =role= object. + - /password_attributes/ :: list of pairs defining an attribute in LDAP and a property of + a Python dictionary representing the LDAP object attributes that will be set + accordingly if such attribute exists in the LDAP entry. As passwords have restricted + access, often one needs only to know that there is a password set on the entry to + perform additional processing. + - /relationships/ :: a dict defining existing relationship criteria associated with the + object. These are used in Web UI to allow filtering of objects by the criteria. The + value is defined as a tuple of an UI label and two prefixes: inclusive and exclusive + that are prepended to the attribute parameter when options are generated by the + framework. LDAPObject defines few default criteria: /member/, /memberof/, + /memberindirect/, /memberofindirect/, and objects can redefine or append more. Due + to regularity of the design of LDAP objects, default criteria already makes it + possible to apply searches almost uniformly: one can ask for membership of a user in + a group, as well as for a membership of a role in a privilege without explicitly + defining those relationships. + + +These properties define how translation would go from Python side to and from an LDAP +backend. + +As an example, let's see how role is defined. This is fully functioning plugin that +provides operations on roles: +#+INCLUDE "role.py" src python -n + +* Extending existing object +As said earlier, until API instance is finalized, objects, methods, and commands can be +added, removed, or modified freely. This allows to extend existing objects. Before API is +finalized, we cannot address objects through the unified interface as =api.Object.foo=, +but for almost all cases an object named =foo= is defined in a plugin +=ipalib.plugins.foo=. + +1. Add new parameter: + #+BEGIN_SRC python -n +from ipalib.plugins.user import user +from ipalib import Str, _ +user.takes_params += ( + Str('foo', + cli_name='foo', + label=_('Foo'), + ), + ) + #+END_SRC +2. Re-define User object label to use organisation-specific terminology in Web UI: + #+BEGIN_SRC python -n +from ipalib.plugins.user import user +from ipalib import text + +_ = text.GettextFactory(domain='extend-ipa') +user.label = _('Staff') +user.label_singular = _('Engineer') + #+END_SRC + Note that we re-defined locally =_= method to use different ~GettextFactory~. As + GettextFactory is supporting a single translation domain, all new translation terms need + to be placed in a separate translation domain and referred accordingly. Python rules for + scoping will keep this symbol as ~<package>._~ and as nobody imports it explicitly, it + will not interfere with the framework's provided ~text._~. +3. Assume =/dev/null= as default shell for all new users: + #+BEGIN_SRC python -n -r +from ipalib.plugins.user import user_add + +def override_default_shell_cb(self, ldap, dn. + entry_attrs, attrs_list, + *keys, **options): + if 'loginshell' in entry_attrs: + default_shell = [self.api.Object.user.params['loginshell'].default] + if entry_attrs['loginshell'] == default_shell: + entry_attrs['loginshell'] = [u'/dev/null'] + +user_add.register_pre_callback(override_default_shell_cb) + #+END_SRC + +The last example exploits a powerful feature available for every method of LDAPObject: +registered callbacks. +* Extending existing method +For objects stored in LDAP database instance all methods support adding callbacks. A +/callback/ is a user-provided function that is called at certain point of execution of a +method. + +There are four types of callbacks: +- /PRE callback/ :: called before executing the method's action. Allows to modify passed + arguments, do additional validation or data transformation and + specific access control beyond what is provided by the framework. +- /POST callback/ :: called after executing the method's action. Allows to analyze results + of the action and perform additional actions or modify output. +- /EXC callback/ :: called in case execution of the method's action caused an execution + error. These callbacks provide means to recover from an erroneous execution. +- /INTERACTIVE callback/ :: called at a client context to allow a command to decide if + additional parameters should be requested from an user. This mechanism especially + useful to simplify complex interaction when there are several levels of possible + scenarios depending on what was provided at a client side. + +All callback types are available to any class derived from =CallbackInterface= +class. These include all LDAP-based CRUD methods. + +Callback registration methods accept a reference to callable and optionally ordering +argument =first= (~False~ by default) to allow the callback be executed before previously +registered callbacks of this type. + +=CallbackInterface= class provides following class methods: +- =register_pre_callback= :: registers /PRE/ callback +- =register_post_callback= :: registers /POST/ callback +- =register_exc_callback= :: registers /EXC/ callback for purpose of recovering from + execution errors +- =register_interactive_prompt_callback= :: registers callbacks called by the client + context. + +Let's look again at the last example: +#+BEGIN_SRC python -n -r +from ipalib.plugins.user import user_add + +def override_default_shell_cb(self, ldap, dn. + entry_attrs, attrs_list, + *keys, **options): + if 'loginshell' in entry_attrs: + default_shell = [self.api.Object.user.params['loginshell'].default] + if entry_attrs['loginshell'] == default_shell: + entry_attrs['loginshell'] = [u'/dev/null'] + +user_add.register_pre_callback(override_default_shell_cb) +#+END_SRC + +This extension defines a pre-processing callback that accepts number of arguments: +- /ldap/ :: reference to the back end to store and retrieve the object's data +- /dn/ :: reference to the object data in LDAP +- /entry_attrs/ :: arguments and options of the command and their values as a + dictionary. All values in /entry_attrs/ will be used for communicating + with LDAP store, thus replacing values should be done with care. For + details please see Python LDAP module documentation +- /attrs_list/ :: list of all attributes we intend to fetch from the back end +- /keys/ :: arguments of the command +- /options/ :: all other unidentified parameters passed to the method + +Arguments of a post-processing callback, /POST/, are slightly different. As action is +already performed and the attributes of the entry are fetched back from the back end, +there is no need to provide =attrs_list=: +#+BEGIN_SRC python -n -r +from ipalib.plugins.user import user_add +def verify_shell_cb(self, ldap, dn. entry_attrs, + *keys, **options): + if 'loginshell' in entry_attrs: + default_shell = [self.api.Object.user.params['loginshell'].default] + if entry_attrs['loginshell'] == default_shell: + # report that default shell is assigned + +user_add.register_post_callback(verify_shell_cb) +#+END_SRC + +Execution error callback, /EXC/, has following signature: +#+BEGIN_SRC python -n +def user_add_error_cb(self, args, options, exc, + call_func, *call_args, **call_kwargs): + return +#+END_SRC + +where arguments have following meaning: +- /args/ :: arguments of the original method +- /options/ :: options of the original method +- /exc/ :: exception object thrown by a /call_func/ +- /call_func/ :: function that was called by the method and caused the error of + execution. In case of LDAP-based methods this is often =ldap.add_entry()= + or =ldap.modify_entry()=, or a similar function +- /call_args/ :: first argument passed to the /call_func/ +- /call_kwargs/ :: remaining arguments of /call_func/ + +Finally, interactive prompt callback receives /kw/ argument which is a dictionary of all +arguments of the command. + +All callbacks are supplied with a reference to the method instance, ~self~, unless the +callback itself has an attribute called '~im_self~'. As can be seen in callback examples, +self reference recursively provides access to the whole FreeIPA API structure. + +This approach gives complete control of existing FreeIPA methods without +deep dive into details of LDAP programming even if the framework allows such a deep dive. + +* Web UI +FreeIPA framework has two major client applications: Web UI and command line-based client +tool, ~ipa~. Web UI communicates with a FreeIPA server running WSGI application that +accepts JSON-formatted requests and translates them to calls to FreeIPA plugins. + +A following code in ~install/share/ui/wsgi.py~ defines FreeIPA web application: +#+INCLUDE "wsgi.py" src python -n -r + +At line [[(wsgi-app-bootstrap)]] we set up FreeIPA framework with server context. This means +plugins are loaded and initialized from following locations: +- ~ipalib/plugins/~ -- general FreeIPA plugins, available for all contexts +- ~ipaserver/plugins/~ -- server-specific plugins, available in '~server~' context + +With =api.finalize()= call at line [[(wsgi-app-finalize)]] FreeIPA framework is locked down and all +components provided by plugins are registered at ~api~ name spaces: =api.Object=, +=api.Method=, =api.Command=, =api.Backend=. + +At this point, ~api~ name spaces become usable and our WSGI entry point, defined on lines +[[(wsgi-app-start)]] to [[(wsgi-app-end)]] can access =api.Backend.session()= to generate +response for WSGI request. + +Web UI itself is written in JavaScript and utilizes JQuery framework. It can be split into +three major parts: +- /communication/ :: tools defined in ~ipa.js~ to allow talking with FreeIPA server using + AJAX requests and JSON formatting +- /presentation/ :: tools in ~facet.js~, ~entity.js~, ~search.js~, ~widget.js~, ~add.js~, + and ~details.js~ to give basic building blocks of Web UI +- /objects/ :: actual implementation of Web UI for FreeIPA objects (user, group, host, + rule, and other available objects registered at =api.Object= by the server + side) + +The code of these JavaScript files is loaded in ~index.html~ and kicked into work by +~webui.js~ where main navigation and document's ~onready~ event handler are defined. In +addition, ~index.html~ imports ~extension.js~ file where all extensions to Web UI can be +registered or referenced. As ~extension.js~ is loaded after all other Web UI JavaScript +files but before ~webui.js~, it can already use all tools of the Web UI. + +The execution of Web UI starts with the call of =IPA.init()= function which does +following: +1. Set up AJAX asynchronous communication via POST method using JSON format. +2. Fetches meta-data about FreeIPA methods available on the server using JSON format and + makes them available as =IPA.methods=. +3. Fetches meta-data about FreeIPA objects available on the server using JSON format and + makes them available as =IPA.objects=. +4. Fetches translations of messages used in the Web UI and makes them available as + =IPA.messages=. +5. Fetches identity of the user running the Web UI, accessible as =IPA.whoami=. +6. Fetches FreeIPA environment specific for Web UI, accessible as =IPA.env=. + +The communication with FreeIPA server is done using =IPA.command()= function. Commands +created with =IPA.command()= can later be executed with =execute()= method. This +separation of construction and actual execution allows to create multiple commands and +combine them together in a single request. Batch requests are created with +=IPA.batch_command()= function and command are added to them with =add_command()= +method. In addition, FreeIPA Web UI allows to run commands concurrently with +=IPA.concurrent_command()= function. + +Web UI has following DOM structure: +|-----------------------+-----------------------------------+------------+-----------| +| | Container | | | +|-----------------------+-----------------------------------+------------+-----------| +| background | header | navigation | content | +| background-header | header-logo | | | +| background-navigation | header-network-activity-indicator | | | +| background-left | loggedinas | | | +| background-right | | | | +|-----------------------+-----------------------------------+------------+-----------| + +~Container~ div is a top-level one, it includes background, header, navigation, content +divs. These divs and their parts can be manipulated from the JavaScript code to represent +the UI. However, FreeIPA gives an easier way to accomplish this. + +** Facets +Facet is a smallest block of FreeIPA Web UI. When facet is defined, it has name, label, +link to an entity it is part of, and methods to create, show, load, and hide itself. + +** Entities +Entity is addressable group of facets. FreeIPA Web UI provides a declarative way of +creating entities and defining their facets based on JavaScript's syntax. Following +example is a complete definition of a netgroup facet: +#+INCLUDE "netgroup.js" src js2-mode -n + +This definition of a netgroup facet describes: +- /details facet/ :: a facet named '~identity~' and three fields, ~cn~, ~description~, + and ~nisdomainname~. In addition, ~description~ field is a text area widget. This + facet is used to display existing netgroup information. +- /association facets/ :: number of facets, linking this one with others. In case of a + netgroup, netgroups are linked to facet group ~member~ via different attributes. The + definition also adds standard association facets defined in ~entity.js~. +- /adder dialog/ :: a dialog to create a new netgroup. The dialog has two fields: ~cn~ and + ~description~ where ~description~ is again a text area widget. + +Similarly to FreeIPA core framework, created entity needs to be registered to the Web UI +via =IPA.register()= method. + +In order to add new entity to the Web UI, one can use ~extension.js~. This file in +~/usr/share/ipa/html~ is empty and provided specifically for this purpose. + +As an example, let's define an entity 'Tank' corresponding to our aquarium tank: +#+BEGIN_SRC js2-mode -n +IPA.tank = {}; +IPA.tank.entity = function(spec) { + var that = IPA.entity(spec); + that.init = function(params) { + details_facet({ + sections: [ + { + name: 'identity', + fields: [ + 'species', 'height', 'width', 'depth' + ] + } + ] + }). + standard_association_facets(). + adder_dialog({ + fields: [ + 'species', 'height', 'width', 'depth' + ] + }); + }; +}; + +IPA.register('tank', IPA.tank.entity); +#+END_SRC + +* Command line tools +As an alternative to Web UI, FreeIPA server can be controlled via command-line interface +provided by the ~ipa~ utility. This utility is operating under '~client~' context and +looks even simpler than Web UI's ~wsgi.py~: +#+BEGIN_SRC python -n +import sys +from ipalib import api, cli + +if __name__ == '__main__': + cli.run(api) +#+END_SRC + +=cli.run()= is the central running point defined in ~ipalib/cli.py~: +#+BEGIN_SRC python -n +# <cli.py code> .... +cli_plugins = ( + cli, + textui, + console, + help, + show_mappings, +) + +def run(api): + error = None + try: + (options, argv) = api.bootstrap_with_global_options(context='cli') + for klass in cli_plugins: + api.register(klass) + api.load_plugins() + api.finalize() + if not 'config_loaded' in api.env: + raise NotConfiguredError() + sys.exit(api.Backend.cli.run(argv)) + except KeyboardInterrupt: + print '' + api.log.info('operation aborted') + except PublicError, e: + error = e + except StandardError, e: + api.log.exception('%s: %s', e.__class__.__name__, str(e)) + error = InternalError() + if error is not None: + assert isinstance(error, PublicError) + api.log.error(error.strerror) + sys.exit(error.rval) +#+END_SRC + +As with WSGI, =api= is bootstraped, though with a client context and using global options +from ~/etc/ipa/default.conf~, and command line arguments. In addition to common plugins +available in ~ipalib/plugins~, ~cli.py~ adds few command-line specific classes defined in +the module itself: +- ~cli~ :: a backend for executing from command line interface which does translation of + command line option names, basic verification of commands and fallback to show + help messages with ~help~ command, execution of the command, and translation of + the output to command-line friendly format if this is defined for the command. +- ~textui~ :: a backend to nicely format output to stdout which handles conversion from + binary to base64, prints text word-wrapped to the terminal width, formats + returned complex values so that they can be easily understood by a human + being. + #+BEGIN_EXAMPLE +>>> entry = {'name' : u'Test example', 'age' : u'100'} +>>> api.Backend.textui.print_entry(entry) + age: 100 + name: Test example + #+END_EXAMPLE +- ~console~ :: starts interactive Python console with FreeIPA commands +- ~help~ :: generates help for every command and method of FreeIPA and structures it into + sections according to the registered FreeIPA objects. + #+BEGIN_EXAMPLE +>>> api.Command.help(u'user-show') +Purpose: Display information about a user. +Usage: ipa [global-options] user-show LOGIN [options] + +Options: +-h, --help show this help message and exit +--rights Display the access rights of this entry (requires --all). See + ipa man page for details. +--all Retrieve and print all attributes from the server. Affects + command output. +--raw Print entries as stored on the server. Only affects output + format. + #+END_EXAMPLE +- ~show_mappings~ :: displays mappings between command's parameters and LDAP attributes: + #+BEGIN_EXAMPLE +>>> api.Command.show_mappings(command_name=u"role-find") +Parameter : LDAP attribute +========= : ============== +name : cn +desc : description +timelimit : timelimit? +sizelimit : sizelimit? + #+END_EXAMPLE + +** Extending command line utility +Since ~ipa~ utility operates under client context, it loads all command plugins from +~ipalib/plugins~. A simple way to extend command line is to drop its plugin file into +~ipalib/plugins~ on the machine where ~ipa~ utility is executed. Next time ~ipa~ is +started, new plugin will be loaded together with all other plugins from ~ipalib/plugins~ +and commands provided by it will be added to the =api=. + +Let's add a command line plugin that allows to ping a server and measures round trip time: +#+BEGIN_SRC python -n +from ipalib import frontend +from ipalib import output +from ipalib import _, ngettext +from ipalib import api +import time + +__doc__ = _(""" +Local extensions to FreeIPA commands +""") + +class timed_ping(frontend.Command): + __doc__ = _('Ping remote FreeIPA server and measure round-trip') + + has_output = ( + output.summary, + ) + def run(self): + t1 = time.time() + result = self.api.Command.ping() + t2 = time.time() + summary = u"""Round-trip to the server is %f ms. +Server response is %s""" + return dict(summary=summary % ((t2-t1)*1000.0, result['summary'])) + +api.register(timed_ping) +#+END_SRC + +When this plugin code is placed into ~ipalib/plugins/extend-cli.py~ (name of the plugin +file can be set arbitrarily), ~ipa timed-ping~ will produce following output: +#+BEGIN_EXAMPLE +$ ipa timed-ping +----------------------------------------------------------------------------- +Round-trip to the server is 286.306143 ms. +Server response is IPA server version 2.1.3GIT8a254ca. API version 2.13 +----------------------------------------------------------------------------- +#+END_EXAMPLE + +In this example we have created ~timed-ping~ command and overrode its =run()= +method. Effectively, this command will only work properly on the client. If the client is +also FreeIPA server (all FreeIPA servers are enrolled as FreeIPA clients), the same code +will also be loaded by the server context and will be accessible to the Web UI as well, +albeit its usefulness will be questionable as it will be measuring the round-trip to the +server from the server itself. + +* File paths +Finally, it should be noted that depending on installed Python version and operating +system, paths where plugins are loaded from may differ. Usually Python extensions are +placed in ~site-packages~ Python sub-directory. In Fedora and RHEL distributions, this is +~/usr/lib/python<version>/site-packages~. Thus, full path to ~extend-cli.py~ would be +~/usr/lib/python<version>/site-packages/ipalib/plugins/extend-cli.py~. + +On recent Fedora distribution, following paths are used: +|--------------------+---------------------------+------------------------------------------------------------| +| Plugins | Python module prefix | File path | +|--------------------+---------------------------+------------------------------------------------------------| +| common | ipalib/plugins | /usr/lib/python2.7/site-packages/ipalib/plugins | +| server | ipaserver/plugins | /usr/lib/python2.7/site-packages/ipaserver/plugins | +| installer, updates | ipaserver/install/plugins | /usr/lib/python2.7/site-packages/ipaserver/install/plugins | +|--------------------+---------------------------+------------------------------------------------------------| + +Next table explains use of contexts in FreeIPA applications: +|---------+------------------+-------------------------+----------------------------------------| +| Context | Application | Plugins | Description | +|---------+------------------+-------------------------+----------------------------------------| +| server | wsgi.py | common, server | Main FreeIPA server, server context | +| cli | ipa | common | Command line interface, client context | +| updates | ipa-ldap-updater | common, server, updates | LDAP schema updater | +|---------+------------------+-------------------------+----------------------------------------| + + +* Platform portability +Originally FreeIPA was created utilizing packages available in Fedora and RHEL +distributions. During configuration stages multiple system services need to be stopped +and started again, scheduled to start after reboot and re-configured. In addition, when +operating system utilizing security measures to harden the server setup, appropriate +activities need to be done as well for preserving proper security contexts. As +configuration details, service names, security features and management tools differ +substantially between various GNU/Linux distributions and other operating systems, porting +FreeIPA project's code to other environment has proven to be problematic. + +When Fedora project has decided to migrate to systemd for services management, FreeIPA +packages for Fedora needed to be updated as well, at the same time preserving support for +older SystemV initialization scheme used in older releases. This prompted to develop a +'platformization' support allowing to abstract services management between different +platforms. + +FreeIPA 2.1.3 includes first cut of platformization work to support Fedora 16 distribution +based on systemd. At the same time, there is an effort to port FreeIPA client side code to +Ubuntu distributions. + +Platform portability in FreeIPA means centralization of code to manage system-provided +services, authentication setup, and means to manage security context and host names. It is +going to be extended in future to cover other areas as well, both client- and server-side. + +The code that implements platform-specific adaptation is placed under +~ipapython/platform~. As of FreeIPA 2.1.3, there are two major "platforms" supported: +- /redhat/ :: Red Hat-based distributions utilizing SystemV init scripts such as Fedora + 15 and RHEL6 +- /fedora16/ :: as name suggests, Fedora 16 and above, are supported by this platform + module. It is based on ~systemd~ system management tool and utilizes + common code in ~ipapython/platform/systemd.py~. ~fedora16.py~ contains + only differentiation required to cover Fedora 16-specific implementation + of systemd use, depending on changes to Dogtag, Tomcat6, and 389-ds + packages. + +Each platform-specific adaptation should provide few basic building blocks: + +*** AuthConfig class + +=AuthConfig= class implements system-independent interface to configure system +authentication resources. In Red Hat systems this is done with authconfig(8) utility. + +=AuthConfig= class is nothing more than a tool to gather configuration options and execute +their processing. These options then converted by an actual implementation to series of a +system calls to appropriate utilities performing real configuration. + +FreeIPA *expects* names of =AuthConfig='s options to follow authconfig(8) naming +scheme. From FreeIPA code perspective, the authentication configuration should be done with +use of ~ipapython.services.authconfig~: + +#+BEGIN_SRC python -n +from ipapython import services as ipaservices + +auth_config = ipaservices.authconfig() +auth_config.disable("ldap").\ + disable("krb5").\ + disable("sssd").\ + disable("sssdauth").\ + disable("mkhomedir").\ + add_option("update").\ + enable("nis").\ + add_parameter("nisdomain","foobar") +auth_config.execute() +#+END_SRC + +The actual implementation can differ. ~redhat~ platform module builds up arguments to +authconfig(8) tool and on =execute()= method runs it with those arguments. Other systems +will need to have processing of the arguments done as defined by authconfig(8) manual +page. This is, perhaps, biggest obstacle on porting FreeIPA client side to the new +platform. + +*** PlatformService class +=PlatformService= class abstracts out an external process running on the system which is +possible to administer: start, stop, check its status, schedule for automatic startup, +etc. + +Services are used thoroughly through FreeIPA server and client install tools. There are +several services that are used especially often and they are selected to be accessible via +Python properties of =ipapython.services.knownservices= instance. + +To facilitate more expressive way of working with often used services, ipapython.services +module provides a shortcut to access them by name via +ipapython.services.knownservices.<service>. A typical code change looks like this: +#+BEGIN_EXAMPLE +from ipapython import services as ipaservices +.... +- service.restart("dirsrv") +- service.restart("krb5kdc") +- service.restart("httpd") ++ ipaservices.knownservices.dirsrv.restart() ++ ipaservices.knownservices.krb5kdc.restart() ++ ipaservices.knownservices.httpd.restart() +#+END_EXAMPLE + +Besides expression change this also makes more explicit to platform providers access to +what services they have to implement. Service names are defined in +ipapython.platform.base.wellknownservices and represent definitive names to access these +services from FreeIPA code. Of course, platform provider should remap those names to +platform-specific ones -- for ipapython.platform.redhat provider mapping is identity. + +Porting to a new platform may be hard as can be witnessed by this example: +https://www.redhat.com/archives/freeipa-devel/2011-September/msg00408.html + +If there is doubt, always consult existing providers. ~redhat.py~ is canonical -- it +represents the code which was used throughout FreeIPA v2 development. + +*** Enabling new platform provider +When support for new platform is implemented and appropriate provider is placed to +~ipapython/platform/~, it is time to enable its use by the FreeIPA. Since FreeIPA is +supposed to be rolled out uniformly on multiple clients and servers, best approach is to +build and distribute software packages using platform-provided package management tools. + +With this in mind, platform code selection in FreeIPA is static and run at package +production time. In order to select proper platform provider, one needs to pass +~SUPPORTED_PLATFORM~ argument to FreeIPA's make process: + +#+BEGIN_EXAMPLE +export SUPPORTED_PLATFORM=fedora16 +# Force re-generate of platform support +rm -f ipapython/services.py +make version-update +make IPA_VERSION_IS_GIT_SNAPSHOT=no all +#+END_EXAMPLE + +~version-update~ target in FreeIPA top-level Makefile will re-create ipapython/services.py +file based on the value of ~SUPPORTED_PLATFORM~ variable. By default this variable is set +to ~redhat~. + +~ipapython/services.py~ is generated using ~ipapython/service.py.in~. In fact, there is +only single line gets replaced in the latter file at the last line: +#+BEGIN_SRC python +# authconfig is an entry point to platform-provided AuthConfig implementation +# (instance of ipapython.platform.base.AuthConfig) +authconfig = None + +# knownservices is an entry point to known platform services +# (instance of ipapython.platform.base.KnownServices) +knownservices = None + +# service is a class to instantiate ipapython.platform.base.PlatformService +service = None + +# restore context default implementation that does nothing +def restore_context_default(filepath): + return + +# Restore security context for a path +# If the platform has security features where context is important, implement your own +# version in platform services +restore_context = restore_context_default + +# Default implementation of backup and replace hostname that does nothing +def backup_and_replace_hostname_default(fstore, statestore, hostname): + return + +# Backup and replace system's hostname +# Since many platforms have their own way how to store system's hostname, this method must be +# implemented in platform services +backup_and_replace_hostname = backup_and_replace_hostname_default + +from ipapython.platform.SUPPORTED_PLATFORM import * +#+END_SRC + +As last statement imports everything from the supported platform provider, all exposed +methods and variables above will be re-defined to platform-specific implementations. This +allows to have FreeIPA framework use of these services separated from the implementation +of the platform. + +The code in ipapython/services.py is going to grow over time when more parts of FreeIPA +framework become platform-independent. diff --git a/doc/guide/netgroup.js b/doc/guide/netgroup.js new file mode 100644 index 000000000..7f79bf84d --- /dev/null +++ b/doc/guide/netgroup.js @@ -0,0 +1,62 @@ +IPA.netgroup = {}; + +IPA.netgroup.entity = function(spec) { + var that = IPA.entity(spec); + that.init = function(params) { + params.builder.search_facet({ + columns: [ + 'cn', + 'description' + ] + }). + details_facet({ + sections: [ + { + name: 'identity', + fields: [ + 'cn', + { + factory: IPA.textarea_widget, + name: 'description' + }, + 'nisdomainname' + ] + } + ] + }). + association_facet({ + name: 'memberhost_host', + facet_group: 'member' + }). + association_facet({ + name: 'memberhost_hostgroup', + facet_group: 'member' + }). + association_facet({ + name: 'memberuser_user', + facet_group: 'member' + }). + association_facet({ + name: 'memberuser_group', + facet_group: 'member' + }). + association_facet({ + name: 'memberof_netgroup', + associator: IPA.serial_associator + }). + standard_association_facets(). + adder_dialog({ + fields: [ + 'cn', + { + factory: IPA.textarea_widget, + name: 'description' + } + ] + }); + }; + + return that; +}; + +IPA.register('netgroup', IPA.netgroup.entity); diff --git a/doc/guide/role.py b/doc/guide/role.py new file mode 100644 index 000000000..068fd84bb --- /dev/null +++ b/doc/guide/role.py @@ -0,0 +1,140 @@ +from ipalib.plugins.baseldap import * +from ipalib import api, Str, _, ngettext +from ipalib import Command +from ipalib.plugins import privilege + +class role(LDAPObject): + """ + Role object. + """ + container_dn = api.env.container_rolegroup + object_name = _('role') + object_name_plural = _('roles') + object_class = ['groupofnames', 'nestedgroup'] + default_attributes = ['cn', 'description', 'member', 'memberof', + 'memberindirect', 'memberofindirect', + ] + attribute_members = { + 'member': ['user', 'group', 'host', 'hostgroup'], + 'memberof': ['privilege'], + } + reverse_members = { + 'member': ['privilege'], + } + rdnattr='cn' + + label = _('Roles') + label_singular = _('Role') + + takes_params = ( + Str('cn', + cli_name='name', + label=_('Role name'), + primary_key=True, + ), + Str('description', + cli_name='desc', + label=_('Description'), + doc=_('A description of this role-group'), + ), + ) + +api.register(role) + + +class role_add(LDAPCreate): + __doc__ = _('Add a new role.') + + msg_summary = _('Added role "%(value)s"') + +api.register(role_add) + + +class role_del(LDAPDelete): + __doc__ = _('Delete a role.') + + msg_summary = _('Deleted role "%(value)s"') + +api.register(role_del) + + +class role_mod(LDAPUpdate): + __doc__ = _('Modify a role.') + + msg_summary = _('Modified role "%(value)s"') + +api.register(role_mod) + + +class role_find(LDAPSearch): + __doc__ = _('Search for roles.') + + msg_summary = ngettext( + '%(count)d role matched', '%(count)d roles matched', 0 + ) + +api.register(role_find) + + +class role_show(LDAPRetrieve): + __doc__ = _('Display information about a role.') + +api.register(role_show) + + +class role_add_member(LDAPAddMember): + __doc__ = _('Add members to a role.') + +api.register(role_add_member) + + +class role_remove_member(LDAPRemoveMember): + __doc__ = _('Remove members from a role.') + +api.register(role_remove_member) + + +class role_add_privilege(LDAPAddReverseMember): + __doc__ = _('Add privileges to a role.') + + show_command = 'role_show' + member_command = 'privilege_add_member' + reverse_attr = 'privilege' + member_attr = 'role' + + has_output = ( + output.Entry('result'), + output.Output('failed', + type=dict, + doc=_('Members that could not be added'), + ), + output.Output('completed', + type=int, + doc=_('Number of privileges added'), + ), + ) + +api.register(role_add_privilege) + + +class role_remove_privilege(LDAPRemoveReverseMember): + __doc__ = _('Remove privileges from a role.') + + show_command = 'role_show' + member_command = 'privilege_remove_member' + reverse_attr = 'privilege' + member_attr = 'role' + + has_output = ( + output.Entry('result'), + output.Output('failed', + type=dict, + doc=_('Members that could not be added'), + ), + output.Output('completed', + type=int, + doc=_('Number of privileges removed'), + ), + ) + +api.register(role_remove_privilege) diff --git a/doc/guide/wsgi.py b/doc/guide/wsgi.py new file mode 100644 index 000000000..2c4a9aaaa --- /dev/null +++ b/doc/guide/wsgi.py @@ -0,0 +1,26 @@ +from ipalib import api +from ipalib.config import Env +from ipalib.constants import DEFAULT_CONFIG + +# Determine what debug level is configured. We can only do this +# by reading in the configuration file(s). The server always reads +# default.conf and will also read in `context'.conf. +env = Env() +env._bootstrap(context='server', log=None) +env._finalize_core(**dict(DEFAULT_CONFIG)) + +# Initialize the API with the proper debug level +api.bootstrap(context='server', debug=env.debug, log=None) (ref:wsgi-app-bootstrap) +try: + api.finalize() (ref:wsgi-app-finalize) +except StandardError, e: + api.log.error('Failed to start IPA: %s' % e) +else: + api.log.info('*** PROCESS START ***') + + # This is the WSGI callable: + def application(environ, start_response): (ref:wsgi-app-start) + if not environ['wsgi.multithread']: + return api.Backend.session(environ, start_response) + else: + api.log.error("IPA does not work with the threaded MPM, use the pre-fork MPM") (ref:wsgi-app-end) |