From a8f9b2ffc5f188dcca646d39a39bba4cdda3e707 Mon Sep 17 00:00:00 2001 From: MuK IT GmbH Date: Mon, 22 Oct 2018 12:41:12 +0000 Subject: [PATCH] publish muk_security - 12.0 --- muk_security/LICENSE | 619 ++++++++++++++++++++ muk_security/__init__.py | 27 + muk_security/__manifest__.py | 54 ++ muk_security/doc/changelog.rst | 9 + muk_security/doc/index.rst | 106 ++++ muk_security/models/__init__.py | 27 + muk_security/models/access_groups.py | 43 ++ muk_security/models/base.py | 40 ++ muk_security/models/ir_model_access.py | 39 ++ muk_security/models/ir_rule.py | 39 ++ muk_security/models/mixins_access.py | 126 ++++ muk_security/models/mixins_access_groups.py | 275 +++++++++ muk_security/models/mixins_locking.py | 96 +++ muk_security/models/res_users.py | 43 ++ muk_security/patch/__init__.py | 20 + muk_security/patch/api.py | 36 ++ muk_security/security/ir.model.access.csv | 3 + muk_security/security/security.xml | 45 ++ muk_security/static/description/banner.png | Bin 0 -> 41114 bytes muk_security/static/description/icon.png | Bin 0 -> 9262 bytes muk_security/static/description/icon.svg | 1 + muk_security/static/description/index.html | 67 +++ muk_security/static/description/logo.png | Bin 0 -> 38064 bytes muk_security/tests/__init__.py | 21 + muk_security/tests/test_access_groups.py | 83 +++ muk_security/tests/test_suspend_security.py | 48 ++ muk_security/tools/__init__.py | 20 + muk_security/tools/security.py | 34 ++ muk_security/views/access_groups.xml | 73 +++ 29 files changed, 1994 insertions(+) create mode 100644 muk_security/LICENSE create mode 100644 muk_security/__init__.py create mode 100644 muk_security/__manifest__.py create mode 100644 muk_security/doc/changelog.rst create mode 100644 muk_security/doc/index.rst create mode 100644 muk_security/models/__init__.py create mode 100644 muk_security/models/access_groups.py create mode 100644 muk_security/models/base.py create mode 100644 muk_security/models/ir_model_access.py create mode 100644 muk_security/models/ir_rule.py create mode 100644 muk_security/models/mixins_access.py create mode 100644 muk_security/models/mixins_access_groups.py create mode 100644 muk_security/models/mixins_locking.py create mode 100644 muk_security/models/res_users.py create mode 100644 muk_security/patch/__init__.py create mode 100644 muk_security/patch/api.py create mode 100644 muk_security/security/ir.model.access.csv create mode 100644 muk_security/security/security.xml create mode 100644 muk_security/static/description/banner.png create mode 100644 muk_security/static/description/icon.png create mode 100644 muk_security/static/description/icon.svg create mode 100644 muk_security/static/description/index.html create mode 100644 muk_security/static/description/logo.png create mode 100644 muk_security/tests/__init__.py create mode 100644 muk_security/tests/test_access_groups.py create mode 100644 muk_security/tests/test_suspend_security.py create mode 100644 muk_security/tools/__init__.py create mode 100644 muk_security/tools/security.py create mode 100644 muk_security/views/access_groups.xml diff --git a/muk_security/LICENSE b/muk_security/LICENSE new file mode 100644 index 0000000..faf7bf4 --- /dev/null +++ b/muk_security/LICENSE @@ -0,0 +1,619 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/muk_security/__init__.py b/muk_security/__init__.py new file mode 100644 index 0000000..74a21b0 --- /dev/null +++ b/muk_security/__init__.py @@ -0,0 +1,27 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +from . import models + +#---------------------------------------------------------- +# Patch System on Load +#---------------------------------------------------------- + +def _patch_system(): + from . import patch \ No newline at end of file diff --git a/muk_security/__manifest__.py b/muk_security/__manifest__.py new file mode 100644 index 0000000..ccdcce8 --- /dev/null +++ b/muk_security/__manifest__.py @@ -0,0 +1,54 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +{ + "name": "MuK Security", + "summary": """Security Features""", + "version": "12.0.1.1.2", + "category": "Extra Tools", + "license": "AGPL-3", + "website": "http://www.mukit.at", + "live_test_url": "https://demo.mukit.at/web/login", + "author": "MuK IT", + "contributors": [ + "Mathias Markl ", + ], + "depends": [ + "muk_utils", + ], + "data": [ + "security/security.xml", + "security/ir.model.access.csv", + "views/access_groups.xml", + ], + "qweb": [ + "static/src/xml/*.xml", + ], + "images": [ + 'static/description/banner.png' + ], + "external_dependencies": { + "python": [], + "bin": [], + }, + "auto_install": True, + "application": False, + "installable": True, + "post_load": "_patch_system", +} \ No newline at end of file diff --git a/muk_security/doc/changelog.rst b/muk_security/doc/changelog.rst new file mode 100644 index 0000000..5a60853 --- /dev/null +++ b/muk_security/doc/changelog.rst @@ -0,0 +1,9 @@ +`1.1.0` +------- + +- Updated dependencies + +`1.0.0` +------- + +- Init version diff --git a/muk_security/doc/index.rst b/muk_security/doc/index.rst new file mode 100644 index 0000000..69e6ab4 --- /dev/null +++ b/muk_security/doc/index.rst @@ -0,0 +1,106 @@ +============ +MuK Security +============ + +Technical module to provide some utility and security features that can be used +in other applications. This module has no direct effect on the running system. + +Installation +============ + +To install this module, you need to: + +Download the module and add it to your Odoo addons folder. Afterward, log on to +your Odoo server and go to the Apps menu. Trigger the debug mode and update the +list by clicking on the "Update Apps List" link. Now install the module by +clicking on the install button. + +Another way to install this module is via the package management for Python +(`PyPI `_). + +To install our modules using the package manager make sure +`odoo-autodiscover `_ is installed +correctly. Then open a console and install the module by entering the following +command: + +``pip install --extra-index-url https://nexus.mukit.at/repository/odoo/simple `` + +The module name consists of the Odoo version and the module name, where +underscores are replaced by a dash. + +**Module:** + +``odoo-addon-`` + +**Example:** + +``sudo -H pip3 install --extra-index-url https://nexus.mukit.at/repository/odoo/simple odoo11-addon-muk-utils`` + +Once the installation has been successfully completed, the app is already in the +correct folder. Log on to your Odoo server and go to the Apps menu. Trigger the +debug mode and update the list by clicking on the "Update Apps List" link. Now +install the module by clicking on the install button. + +The biggest advantage of this variant is that you can now also update the app +using the "pip" command. To do this, enter the following command in your console: + +``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple `` + +When the process is finished, restart your server and update the application in +Odoo. The steps are the same as for the installation only the button has changed +from "Install" to "Upgrade". + +You can also view available Apps directly in our `repository `_ +and find a more detailed installation guide on our `website `_. + +For modules licensed under OPL-1, you will receive access data when you purchase +the module. If the modules were not purchased directly from +`MuK IT `_ please contact our support (support@mukit.at) +with a confirmation of purchase to receive the corresponding access data. + +Upgrade +============ + +To upgrade this module, you need to: + +Download the module and add it to your Odoo addons folder. Restart the server +and log on to your Odoo server. Select the Apps menu and upgrade the module by +clicking on the upgrade button. + +If you installed the module using the "pip" command, you can also update the +module in the same way. Just type the following command into the console: + +``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple `` + +When the process is finished, restart your server and update the application in +Odoo, just like you would normally. + +Configuration +============= + +No additional configuration is needed to use this module. + +Usage +============= + +This module has no direct visible effect on the system. It provide security features. + +Credits +======= + +Contributors +------------ + +* Mathias Markl + +Author & Maintainer +------------------- + +This module is maintained by the `MuK IT GmbH `_. + +MuK IT is an Austrian company specialized in customizing and extending Odoo. +We develop custom solutions for your individual needs to help you focus on +your strength and expertise to grow your business. + +If you want to get in touch please contact us via mail +(sale@mukit.at) or visit our website (https://mukit.at). diff --git a/muk_security/models/__init__.py b/muk_security/models/__init__.py new file mode 100644 index 0000000..c9138ef --- /dev/null +++ b/muk_security/models/__init__.py @@ -0,0 +1,27 @@ +################################################################################### +# +# Copyright (C) 2018 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +from . import base +from . import mixins_locking +from . import mixins_access +from . import mixins_access_groups +from . import access_groups +from . import ir_model_access +from . import res_users +from . import ir_rule diff --git a/muk_security/models/access_groups.py b/muk_security/models/access_groups.py new file mode 100644 index 0000000..05c7b65 --- /dev/null +++ b/muk_security/models/access_groups.py @@ -0,0 +1,43 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +from odoo import models, fields, api + +class AccessGroups(models.Model): + + _name = 'muk_security.access_groups' + _description = "Record Access Groups" + _inherit = 'muk_utils.mixins.groups' + + #---------------------------------------------------------- + # Database + #---------------------------------------------------------- + + perm_read = fields.Boolean( + string='Read Access') + + perm_create = fields.Boolean( + string='Create Access') + + perm_write = fields.Boolean( + string='Write Access') + + perm_unlink = fields.Boolean( + string='Unlink Access') + \ No newline at end of file diff --git a/muk_security/models/base.py b/muk_security/models/base.py new file mode 100644 index 0000000..501f68c --- /dev/null +++ b/muk_security/models/base.py @@ -0,0 +1,40 @@ +################################################################################### +# +# Copyright (C) 2018 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import logging + +from odoo import api, models, fields + +from odoo.addons.muk_security.tools.security import NoSecurityUid + +_logger = logging.getLogger(__name__) + +class Base(models.AbstractModel): + + _inherit = 'base' + + @api.model + def suspend_security(self, user=None): + return self.sudo(user=NoSecurityUid(user or self.env.uid)) + + @api.model + def check_field_access_rights(self, operation, fields): + if isinstance(self.env.uid, NoSecurityUid): + return fields or list(self._fields) + return super(Base, self).check_field_access_rights(operation, fields) \ No newline at end of file diff --git a/muk_security/models/ir_model_access.py b/muk_security/models/ir_model_access.py new file mode 100644 index 0000000..43d209a --- /dev/null +++ b/muk_security/models/ir_model_access.py @@ -0,0 +1,39 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import logging + +from odoo import api, fields, models +from odoo import tools, _ +from odoo.exceptions import ValidationError + +from odoo.addons.muk_security.tools.security import NoSecurityUid + +_logger = logging.getLogger(__name__) + +class IrModelAccess(models.Model): + + _inherit = 'ir.model.access' + + @api.model + @tools.ormcache_context('self._uid', 'model', 'mode', 'raise_exception', keys=('lang',)) + def check(self, model, mode='read', raise_exception=True): + if isinstance(self.env.uid, NoSecurityUid): + return True + return super(IrModelAccess, self).check(model, mode=mode, raise_exception=raise_exception) \ No newline at end of file diff --git a/muk_security/models/ir_rule.py b/muk_security/models/ir_rule.py new file mode 100644 index 0000000..3c7d72f --- /dev/null +++ b/muk_security/models/ir_rule.py @@ -0,0 +1,39 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import logging + +from odoo import api, fields, models +from odoo import tools, _ +from odoo.exceptions import ValidationError + +from odoo.addons.muk_security.tools.security import NoSecurityUid + +_logger = logging.getLogger(__name__) + +class IrRule(models.Model): + + _inherit = 'ir.rule' + + @api.model + @tools.ormcache('self._uid', 'model_name', 'mode') + def _compute_domain(self, model_name, mode="read"): + if isinstance(self.env.uid, NoSecurityUid): + return None + return super(IrRule, self)._compute_domain(model_name, mode=mode) \ No newline at end of file diff --git a/muk_security/models/mixins_access.py b/muk_security/models/mixins_access.py new file mode 100644 index 0000000..4172579 --- /dev/null +++ b/muk_security/models/mixins_access.py @@ -0,0 +1,126 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import logging + +from odoo import _ +from odoo import models, api, fields +from odoo.exceptions import AccessError + +_logger = logging.getLogger(__name__) + +class AccessModel(models.AbstractModel): + + _name = 'muk_security.mixins.access' + _description = 'Access Mixin' + + #---------------------------------------------------------- + # Database + #---------------------------------------------------------- + + permission_read = fields.Boolean( + compute='_compute_permissions_read', + search='_search_permission_read', + string="Read Access") + + permission_create = fields.Boolean( + compute='_compute_permissions_create', + search='_search_permission_create', + string="Create Access") + + permission_write = fields.Boolean( + compute='_compute_permissions_write', + search='_search_permission_write', + string="Write Access") + + permission_unlink = fields.Boolean( + compute='_compute_permissions_unlink', + search='_search_permission_unlink', + string="Delete Access") + + #---------------------------------------------------------- + # Function + #---------------------------------------------------------- + + @api.multi + def check_access(self, operation, raise_exception=False): + try: + access_right = self.check_access_rights(operation, raise_exception) + access_rule = self.check_access_rule(operation) is None + return access_right and access_rule + except AccessError: + if raise_exception: + raise + return False + + #---------------------------------------------------------- + # Search + #---------------------------------------------------------- + + @api.model + def _search_permission_read(self, operator, operand): + records = self.search([]).filtered(lambda r: r.check_access('read') == True) + if operator == '=' and operand: + return [('id', 'in', records.mapped('id'))] + return [('id', 'not in', records.mapped('id'))] + + @api.model + def _search_permission_create(self, operator, operand): + records = self.search([]).filtered(lambda r: r.check_access('create') == True) + if operator == '=' and operand: + return [('id', 'in', records.mapped('id'))] + return [('id', 'not in', records.mapped('id'))] + + @api.model + def _search_permission_write(self, operator, operand): + records = self.search([]).filtered(lambda r: r.check_access('write') == True) + if operator == '=' and operand: + return [('id', 'in', records.mapped('id'))] + return [('id', 'not in', records.mapped('id'))] + + @api.model + def _search_permission_unlink(self, operator, operand): + records = self.search([]).filtered(lambda r: r.check_access('unlink') == True) + if operator == '=' and operand: + return [('id', 'in', records.mapped('id'))] + return [('id', 'not in', records.mapped('id'))] + + #---------------------------------------------------------- + # Read, View + #---------------------------------------------------------- + + @api.multi + def _compute_permissions_read(self): + for record in self: + record.update({'permission_read': record.check_access('read')}) + + @api.multi + def _compute_permissions_create(self): + for record in self: + record.update({'permission_create': record.check_access('create')}) + + @api.multi + def _compute_permissions_write(self): + for record in self: + record.update({'permission_write': record.check_access('write')}) + + @api.multi + def _compute_permissions_unlink(self): + for record in self: + record.update({'permission_unlink': record.check_access('unlink')}) \ No newline at end of file diff --git a/muk_security/models/mixins_access_groups.py b/muk_security/models/mixins_access_groups.py new file mode 100644 index 0000000..6119a93 --- /dev/null +++ b/muk_security/models/mixins_access_groups.py @@ -0,0 +1,275 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import logging + +from odoo import _, SUPERUSER_ID +from odoo import models, api, fields +from odoo.exceptions import AccessError + +from odoo.addons.muk_security.tools.security import NoSecurityUid + +_logger = logging.getLogger(__name__) + +class BaseModelAccessGroups(models.AbstractModel): + + _name = 'muk_security.mixins.access_groups' + _description = "MuK Access Groups Model" + _inherit = 'muk_security.mixins.access' + + # Set it to True to enforced security even if no group has been set + _strict_security = False + + # If set the group fields are restricted by the access group + _field_groups = None + + # If set the suspend fields are restricted by the access group + _suspend_groups = None + + #---------------------------------------------------------- + # Datebase + #---------------------------------------------------------- + + @api.model + def _add_magic_fields(self): + super(BaseModelAccessGroups, self)._add_magic_fields() + def add(name, field): + if name not in self._fields: + self._add_field(name, field) + model = self._name.split(".")[-1] + add('suspend_security_read', fields.Boolean( + _module=self._module, + string="Suspend Security for Read", + automatic=True, + default=False, + groups=self._suspend_groups)) + add('suspend_security_create', fields.Boolean( + _module=self._module, + string="Suspend Security for Create", + automatic=True, + default=False, + groups=self._suspend_groups)) + add('suspend_security_write', fields.Boolean( + _module=self._module, + string="Suspend Security for Write", + automatic=True, + default=False, + groups=self._suspend_groups)) + add('suspend_security_unlink', fields.Boolean( + _module=self._module, + string="Suspend Security for Unlink", + automatic=True, + default=False, + groups=self._suspend_groups)) + add('groups', fields.Many2many( + _module=self._module, + comodel_name='muk_security.groups', + relation='muk_groups_%s_rel' % model, + column1='aid', + column2='gid', + string="Groups", + automatic=True, + groups=self._field_groups)) + add('complete_groups', fields.Many2many( + _module=self._module, + comodel_name='muk_security.groups', + relation='muk_groups_complete_%s_rel' % model, + column1='aid', + column2='gid', + string="Complete Groups", + compute='_compute_groups', + store=True, + automatic=True, + groups=self._field_groups)) + + #---------------------------------------------------------- + # Function + #---------------------------------------------------------- + + @api.model + def _get_no_access_ids(self): + model = self._name.split(".")[-1] + if not self._strict_security: + sql = ''' + SELECT id + FROM %s a + WHERE NOT EXISTS ( + SELECT * + FROM muk_groups_complete_%s_rel r + WHERE r.aid = a.id + ); + ''' % (self._table, model) + self.env.cr.execute(sql) + fetch = self.env.cr.fetchall() + return len(fetch) > 0 and list(map(lambda x: x[0], fetch)) or [] + else: + return [] + + @api.model + def _get_suspended_access_ids(self, operation): + model = self._name.split(".")[-1] + sql = ''' + SELECT id + FROM %s a + WHERE a.suspend_security_%s = true + ''' % (self._table, operation) + self.env.cr.execute(sql) + fetch = self.env.cr.fetchall() + return len(fetch) > 0 and list(map(lambda x: x[0], fetch)) or [] + + @api.model + def _get_access_ids(self): + model = self._name.split(".")[-1] + sql = ''' + SELECT r.aid + FROM muk_groups_complete_%s_rel r + JOIN muk_security_groups g ON r.gid = g.id + JOIN muk_security_groups_users_rel u ON r.gid = u.gid + WHERE u.uid = %s AND g.perm_read = true + ''' % (model, self.env.user.id) + self.env.cr.execute(sql) + fetch = self.env.cr.fetchall() + access_ids = len(fetch) > 0 and list(map(lambda x: x[0], fetch)) or [] + return access_ids + + @api.model + def _get_ids_without_security(self, operation): + no_access_ids = self._get_no_access_ids() + suspended_access_ids = self._get_suspended_access_ids(operation) + return list(set(no_access_ids).union(suspended_access_ids)) + + @api.model + def _get_complete_access_ids(self, operation): + access_ids = self._get_access_ids() + no_access_ids = self._get_no_access_ids() + suspended_access_ids = self._get_suspended_access_ids(operation) + return list(set(access_ids).union(no_access_ids, suspended_access_ids)) + + @api.multi + def _eval_access_skip(self, operation): + if isinstance(self.env.uid, NoSecurityUid): + return True + return False + + @api.multi + def check_access_groups(self, operation): + if self.env.user.id == SUPERUSER_ID or self._eval_access_skip(operation): + return None + model = self._name.split(".")[-1] + filter_ids = self._get_ids_without_security(operation) + for record in self.filtered(lambda rec: rec.id not in filter_ids): + sql = ''' + SELECT perm_%s + FROM muk_groups_complete_%s_rel r + JOIN muk_security_groups g ON g.id = r.gid + JOIN muk_security_groups_users_rel u ON u.gid = g.id + WHERE r.aid = %s AND u.uid = %s + ''' % (operation, model, record.id, self.env.user.id) + self.env.cr.execute(sql) + fetch = self.env.cr.fetchall() + if not any(list(map(lambda x: x[0], fetch))): + raise AccessError(_("This operation is forbidden!")) + + @api.multi + def check_access(self, operation, raise_exception=False): + res = super(BaseModelAccessGroups, self).check_access(operation, raise_exception) + try: + access_groups = self.check_access_groups(operation) == None + access = res and access_groups + if not access and raise_exception: + raise AccessError(_("This operation is forbidden!")) + return access + except AccessError: + if raise_exception: + raise AccessError(_("This operation is forbidden!")) + return False + + #---------------------------------------------------------- + # Read + #---------------------------------------------------------- + + @api.multi + def _after_read(self, result, *largs, **kwargs): + result = super(BaseModelAccessGroups, self)._after_read(result) + if self.env.user.id == SUPERUSER_ID or self._eval_access_skip("read"): + return result + access_ids = self._get_complete_access_ids("read") + result = [result] if not isinstance(result, list) else result + if len(access_ids) > 0: + access_result = [] + for record in result: + if record['id'] in access_ids: + access_result.append(record) + return access_result + return [] + + @api.model + def _after_search(self, result, *largs, **kwargs): + result = super(BaseModelAccessGroups, self)._after_search(result) + if self.env.user.id == SUPERUSER_ID or self._eval_access_skip("read"): + return result + access_ids = self._get_complete_access_ids("read") + if len(access_ids) > 0: + access_result = self.env[self._name] + if isinstance(result, int): + if result in access_ids: + return result + else: + for record in result: + if record.id in access_ids: + access_result += record + return access_result + return self.env[self._name] + + @api.model + def _after_name_search(self, result, *largs, **kwargs): + result = super(BaseModelAccessGroups, self)._after_name_search(result) + if self.env.user.id == SUPERUSER_ID or self._eval_access_skip("read"): + return result + access_ids = self._get_complete_access_ids("read") + if len(access_ids) > 0: + access_result = [] + for tuple in result: + if tuple[0] in access_ids: + access_result.append(tuple) + return access_result + return [] + + #---------------------------------------------------------- + # Read, View + #---------------------------------------------------------- + + @api.depends('groups') + def _compute_groups(self): + for record in self: + record.complete_groups = record.groups + + #---------------------------------------------------------- + # Create, Update, Delete + #---------------------------------------------------------- + + @api.multi + def _before_write(self, vals, *largs, **kwargs): + self.check_access_groups('write') + return super(BaseModelAccessGroups, self)._before_write(vals, *largs, **kwargs) + + @api.multi + def _before_unlink(self, *largs, **kwargs): + self.check_access_groups('unlink') + return super(BaseModelAccessGroups, self)._before_unlink(*largs, **kwargs) \ No newline at end of file diff --git a/muk_security/models/mixins_locking.py b/muk_security/models/mixins_locking.py new file mode 100644 index 0000000..3179664 --- /dev/null +++ b/muk_security/models/mixins_locking.py @@ -0,0 +1,96 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import os +import hashlib +import logging +import itertools + +from odoo import _, SUPERUSER_ID +from odoo import models, api, fields +from odoo.exceptions import AccessError + +_logger = logging.getLogger(__name__) + +class LockingModel(models.AbstractModel): + + _name = 'muk_security.mixins.locking' + _description = 'Locking Mixin' + + #---------------------------------------------------------- + # Database + #---------------------------------------------------------- + + locked_by = fields.Many2one( + comodel_name='res.users', + string="Locked by") + + is_locked = fields.Boolean( + compute='_compute_locked', + string="Locked") + + is_lock_editor = fields.Boolean( + compute='_compute_locked', + string="Editor") + + #---------------------------------------------------------- + # Locking + #---------------------------------------------------------- + + @api.multi + def lock(self): + self.write({'locked_by': self.env.uid}) + + @api.multi + def unlock(self): + self.write({'locked_by': None}) + + @api.multi + def check_lock(self, *largs, **kwargs): + for record in self: + if record.locked_by.exists() and not record.locked_by.id in (self.env.uid, SUPERUSER_ID): + raise AccessError(_("The record (%s [%s]) is locked, by an other user.") % (record._description, record.id)) + + #---------------------------------------------------------- + # Read, View + #---------------------------------------------------------- + + @api.depends('locked_by') + def _compute_locked(self): + for record in self: + if record.locked_by.exists(): + record.update({'is_locked': True, 'is_lock_editor': record.locked_by.id == record.env.uid}) + else: + record.update({'is_locked': False, 'is_lock_editor': False}) + + #---------------------------------------------------------- + # Create, Update, Delete + #---------------------------------------------------------- + + @api.multi + def write(self, vals): + self.check_lock() + return super(BaseModelLocking, self).write(vals) + + + @api.multi + def unlink(self): + self.check_lock() + return super(BaseModelLocking, self).unlink() + diff --git a/muk_security/models/res_users.py b/muk_security/models/res_users.py new file mode 100644 index 0000000..b42e878 --- /dev/null +++ b/muk_security/models/res_users.py @@ -0,0 +1,43 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import logging + +from odoo import api, fields, models +from odoo import tools, _ +from odoo.exceptions import ValidationError + +from odoo.addons.muk_security.tools.security import NoSecurityUid + +_logger = logging.getLogger(__name__) + +class AccessUser(models.Model): + + _inherit = 'res.users' + + #---------------------------------------------------------- + # Functions + #---------------------------------------------------------- + + @classmethod + def _browse(cls, ids, env, prefetch=None): + return super(AccessUser, cls)._browse([ + id if not isinstance(id, NoSecurityUid) + else super(NoSecurityUid, id).__int__() + for id in ids], env, prefetch=prefetch) \ No newline at end of file diff --git a/muk_security/patch/__init__.py b/muk_security/patch/__init__.py new file mode 100644 index 0000000..a42d175 --- /dev/null +++ b/muk_security/patch/__init__.py @@ -0,0 +1,20 @@ +################################################################################### +# +# Copyright (C) 2018 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +from . import api \ No newline at end of file diff --git a/muk_security/patch/api.py b/muk_security/patch/api.py new file mode 100644 index 0000000..d090a97 --- /dev/null +++ b/muk_security/patch/api.py @@ -0,0 +1,36 @@ +################################################################################### +# +# Copyright (C) 2018 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import logging + +from odoo import models, api, SUPERUSER_ID + +from odoo.addons.muk_utils.tools import patch +from odoo.addons.muk_security.tools import security + +_logger = logging.getLogger(__name__) + +@api.model +@patch.monkey_patch(api.Environment) +def __call__(self, cr=None, user=None, context=None): + env = __call__.super(self, cr, user, context) + if user and isinstance(user, security.NoSecurityUid): + env.uid = user + return env + return env \ No newline at end of file diff --git a/muk_security/security/ir.model.access.csv b/muk_security/security/ir.model.access.csv new file mode 100644 index 0000000..d898e93 --- /dev/null +++ b/muk_security/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink + +access_security_access_groups_user,access_security_access_groups_user,model_muk_security_access_groups,base.group_user,1,1,1 \ No newline at end of file diff --git a/muk_security/security/security.xml b/muk_security/security/security.xml new file mode 100644 index 0000000..704bfed --- /dev/null +++ b/muk_security/security/security.xml @@ -0,0 +1,45 @@ + + + + + + + + User can only edit and delete their own groups. + + + + + + + [('create_uid','=',user.id)] + + + + Admins can edit and delete all groups. + + + + + + + [(1 ,'=', 1)] + + + + diff --git a/muk_security/static/description/banner.png b/muk_security/static/description/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..a73ca8323e67c60b5253cd31a34e770b3de083e2 GIT binary patch literal 41114 zcmeGERX|-!(=80+1c%^m!95V%HE3{mg1b8`LU4=V5`qT{5Zv9}-QC^on=9FQ-u=A) zIoJQqxq}6Bc6W94s8OS;SD2!L6bd2%A_N2kiu7A?We5mp2M7qrnip`uCon^>G!PJE z5Yplzsvi0Wi?Et%((7<132?Aw_1=e-^*RT32dKzcIvv6XM@L7xz-LFO$mrtv$4TXD z@C!%_O$pA+p5J`kgt*Oz2JDBPo}2ib%^2qt7SU005EVgCSl=OFX@wzw|H$eIi3G>} zm6Ve6(|>*b9rWtE5~#eoQc_cs+b`&2SI^FdA^bC`(E1x^C_ch$nS+m9>5nJu+~PF8 zbdMU;u>)}!<8G;A(!NC>|LaApq>xcix@D>OpL$wRrG5&9D;3x;zD}$@CK45GSDiZ0 zC}Di+`}A^g+wR`^ov62tN7YYMLTlMbZD%|KXg}Hsnx9F_<>aWkwK3C8emkm-nvuf` zen*^D>GjywDTwbC0(m=~WNpJ}DT=-|-V^BzFLYG5kj33&J+^sKoxOE+PdR@Uq%X8F zI+nb-?j;;pcGq8>j=ri&oGvH7(rt*4&$G8!z*&0x#~S|_rh^4M=k>8$MGh%AkD`T= zifZY!mxf;ExRJWfFIWWq)U;lCfyygNTTZs(K2xbFM{2oqchynA*RZ>%XE{{4jIpaw zxqVDLt-CnE*7v=memqzzM60H1&KloTL(N55or2dn(`YE)khu8{y+N@=-w<4YP3A~ZfCRN zXkZMpd=Vf(x>a9s8OK|Z+?Pc}1=Jtig?X?1h-DCU}4&!zCfBVdLP%fmM zwmI&rcd_URq)lx_i{_ca+FII%3fFMx8CJtN^)oIqmsEbAp>-!kc}9LZ869x@6V4BH zHEk&2LNeQTsp5@gXJWmju@wwXtlAn#=B4}QfAe94b&&J;J!rkz{eKMxWyO+#)sKvO zZ5fHS+(apn`EjS-K_k|_t>a7QQKih<*UhFKzZK6QkRe|DFyj8e}E3ZgfR2+b;?1fq8FKfxXzaBP93I&4EE7?JOPj zIJZ2X>rPVR-i-OR#j#`E@aLdh)3dC{3wi&a*a7A~^~qygrqkaV0$w<*@s+QPC|bhz z8!@1D)Qy{pOVqt^V6UJ0TCiVjkD=(HAawfWg)Yl07^Qzqg zdgh<_m7pqq-f-0oEYaJxmRNx>`Gf?0&Jot`D|h+ct#*x9JBsd8v@q>VzC}zkE|1;G zk&Rk=yLy1P*S_HjZR{JAIW=aA%X#m9$6X#|2S-#smVd$5chD@t58@TwLYz1^ zw*y}9LabjM)Z|ELx-Ch3!y~nljvM`nh<379Wd6AmcoRSPyKK2BU@={x7C=<>Y#Dm; zuS(0~%zg^q!glbo0>*z97>0ZFAI6jPGu(hkm>7km!{jqQ`c{6kgG&Zm)02|z@>8E^ z^UN%GKXTANUS7eeIB1)+NoHCCriN}_L5AHOBgZMg2E9s5{U9O!n-fCL>|=cos>;70 zmrMchpHxjjI>GI;IS+AdlAZB3 z9Xt-ko@+fgYB|TFk|hFI`?=)6T-WLaFyz+n;C?zLJ^V1BKwC&S{tA`;!r^NbQFj{d zG1WmlHe!Z$2GI_;^Es(cN!6Q^QyqwoYF~<$H?&DyeaD?aYdz5E(XQFE{R}bsY%QI) zduLGp4iAaxpgBkd8$*g{IP_LIEDZHIb!JB5XuWC=k_)XGOh}RD>}96?X{>HL!LGe5 z4W%px1xsnuZ7rQApJ|-34X3jy%(1D5(HIQ$a<{=goKNu-;hVv1*m^KE9MYIZ%J<1N zw-3a*c6|9RxH8rZvU10|xPC%@#y17f$~}P7o;e~i_2G(NHam9_tcbrjc-bp*A zZme_)p<%PXGhUBINQut$7((7TR3^xpB55nXHpsTN!tSLS)C_}e?gS&{&bJHv`v>3~6mx!^7Pm{QD?htgVJ%mO9XEAw-UBoJ(bL`lE(PGcY7Nk#M@soDXBJ(zFWl5ssBgkh0@JmNK(uf<{Vh`##x5?@TAIY zwpBO9NbZY^TN|pVO#RMmV8&A>ISjKKj)O%5T}zoJ&Mg;93jUzk=Xsc#%GlB<7b^^V zC>oPbg_AK3EoRB5Eq6Q|K5%|s(?q!aY;3HZa+>tfQZ!ywItO3S5znK8%gQkvV$3et z_|`0=$TA1N>(dD9g8!ouFUiWN&a#kNw)EOZ)X8w8!LmY+)6>EW=Q4chQKDV)_#nyH z=*e(t^UbjPPIosdXVZ>R!WgAI3p+ZP@GSU_$gTYOch3v#-$i(KwT!F#cO*UkA)ksOD(uuO>#Wuy*N|;9GM?dpy)MHHjU(@(Ob+Xeo+DE_C zB7~OzqO%`Z=Q?621g_lz^~vskRv|0|6~j!V=30sI%%7c3ZdeT1B^t1>3v%DyU(2u( z11lfLN2&tY=13F-bgCVZ7SdmmL=8;I{6k~Lz`Fr$sANM;8KS5p(^ z0sD8vQ&q4TQHfIIArU*k21y9~Yx`M5)|Os3i&-|r#A0Bcv#0`m3V+7?)C~Oo(48od z{r}@B>@N*L>{+d58gz!#z_4boM-Hg}jMYK}SYp6&V%8k7M+cbx(t7_AuuQP=2DCwt zLq#F!X?Y+<;Gf>}6dhpwouDWP*icAVS~4zR`L$8B_kY*iLJD>vWWdA=FnB8)oS6q` zf1g2i@PP;H{XYx%CwJh6{=MIyQh@0%NX9;9{B;!e|Mvi}&Hvj0Ujuc%~}gF{ayWyi^VL= zZ29{S#ZghpL>L?KMYA7^z0t_HxjTMrrg@qjxdBP7a!FavBKN&{$9 zg-ZGaZXYfgwwUz-Cw1J&`1y4=?>)%>KDR{oksN?WDD=Mm@d4!Nz6>$u`)1a@K*C@v0LeK_fZu|IC; z*o;AI>kg*{jDkUp&0U%y((iL8d<=NM7tIzl;D^vicyC%J3zQ+tyJE()5+p(k$8*{J z9$t)Tr4{vX!17kx1(W;M(b+D9ylbLe9~9m1q23;*)@4Q!Z{}tckLo+kUa^1W7E^d>iLSK)|U_OG&N34na(6 zBo`o|(v_t=`-P1&!)cupJ>Xl=pMwV+#G!`n^u|085o{#R%AK2Cj9}V4-J8X-XVze^ zTQUv~)!k#Htw*G zy*G)qfF+ZfAr1s#-_sgaK)9c_aK4g0C=&L%8dIw{X6IJ0ay*XDan4Sv-7!7?`H079 zB$MiT^5KSowZZ>gO_fDa1@szU&4)N$^p}|rp~f(uMHmi%t)^=Ox0+RcvXT0FbDSaGB|j0HOXjN>js$$< z%zwMbr_aEmBH_O30jTi`G7F{#2Su0F?ng=+y6)38wYJ-F$<&C)#eM)P6M@NgfIq9} zkueF?_jvt>CT@wd_jq%8gjVXzEW~$D7q_wl`c(w=Xe42kCIB*fFU)#@N7w`7c2=h~ zop8VnnPDshjhRpfX=YY}T1vIa!UT_1ZCFn1u5#YmwSrZC@4hiSymz)jk|?4>0Tvv$ zTC4{O*z}&jJ9HkOKbKrz=??2UPn4kE#V5aA%!_K^0b16eO{J zyGMt^En??WbD=0Q;M~Kw2z~*481xwc|45kOZUAVgepqS|45lR=!0A{H(t|xTM@d1QYpk1?B*`ow_$pcUdFy|)*m zWVxxmpX>PK>{4f;=^3JAsUf10*nfMNlGus3*5~B0=q$-{ltDv%dAnr!Z4+wq|cHN!wtIcxW8JPBn(RTzu73f;8|t@yyu}8VZ0#d=^E|Mf1uCkyLvEHm-!9zX;`qVd7 zFhX-mE5ilB#by>JC;W+cJL^cmi{db`)^l3_;l&;P_GN)C`e}$q!d{$Gp$sfHA5W)e z%1!%Y+4#2O$_&CnjkCe(-FQ70g+Rt@A44Wq8PbW5OypwqCBQFH!czh;Mxep&*2fs+mgoh-JZ{-g zI{KksrknbR)0G*6(WwX|wnZvGRUI!}Y>P*_N|qnUpw@>Cn%8Xn<#0ovJxa|7jW-73 zsrsmlC~O?eSYLC zmt2XCTcQ_puC2y`x4(o|Ofac(d4^ZQ=Wy|AL4d}RgK+wX0*V}-;0yKmchp3k;M?!w#n0!<1y=)nxDtSW&;jj$T zos&U${sy%+bd0HMIVbl@U(F34s}>2S`w4#sPY3|nbpVJ5kbMVI0mM5qZy=AcY1NLLeuTVts z`Z`p++$pDFw?P7<4Uqnv%ldHRDP4=FLRwUCWg+FWL9OcE{YGs>smEm(?KFWC+!1i@ zc2?&Qq3hh0ElLuDibuXvbu7Q3o7WhSYFgmQTDZI2*RV8WSG<-iMrI|36mOj!Y5tlxd|x@InRKF@y{^(a|AY@?O5JuYnC?8D zRu9MA+&W?^YCpp~gcCg_O`K)F>acC6mtDXq9fK>q<=5!Ym+62&r4;edsAFM4>qf#&5#%(QH-JBuA^!Qp+hp#ti}C{bdCFHC z%~8@6;q-t5Wxn?RWgdG!mMs^CygNo4`8jd*5d$mx#Ce#moZM`sV=bbtOJ0xfiCj4nTY-Dw*0s^6Pqc0T`-(W$On)RbP9-YyC z=*<{pFkbi}&WN4L;Y}W$CJ0}Kneofm)HH|yyWgk-r2qCaV}3=il#iAueyxx(e1B8B z0JfVBRzJ25*Uy-Ql4ZkVp>FIKst68!3l%()!UHqmN%K1rKF~B~107tBdst=qfe9_ZUTK2G53Z(5A{geltno~P>2g=S z9u8oW-46*j?|;k$yN$-4{!p#TZlhX+i)tAi-&XZwu(2sB^M8CDH26-OIP>RB_{~+J zZm%(7!pD#hlk=lTbavJ?w6g-7QPQ&cjo?G?%mw3u;&?@f1RPGtZHNC6OaCe}9GfSH_>FDIIfpRD#)f>n9MY zJRXl4)u|hjekUfGKsY^(0{G(wH98A)DjrN(YwzkHgOR;?9xJsuvwnc}@()>pP6@F) z7qRWBL1fNg=3+kC?GOp(KB&pVd$iH-zV3|`{i@Il$xM7~KsYVFvpImEA!zmn!sF04 zwG7gb=yb`FjBxXZoVVYQX{t&BXIeR&W<6I(fyl1JfVL#}Tj~f0K*5LMH@KZ*ba@=M-9w1910~8X>z}iJiGWrr;(({>#EbzHL zDKl!;DioHn20_s9mb%XJ1Bs+;221yFs#HQi`?Js!+d$Kejxcb4gtQ%fKwi4eE&2=~ z`A^{nGza5*zp^O*3ktK39RrFRnKtlm9Sxm6TPY<}>^i^wNj!_<7%RL%f%=PCF+WyptTcZp1;XU%yj%VLov*WgYWFohGF$7naInI>bV!jd~h285o2%0&{c%|^o*iqSz zgX|mi^M@yxVNr78n$h|l z*S3{9uA)h660B{-32n#oR|KKw8c&mb0bIWeJi-HDC+GX^;%^=|&Z6 zw1DT1;N4<;gR}PWZ!dseR8mCc{Dhd64$iw2P8w6r2m1^ zbRf_?*=Vr?x(yLzAUyY}eC!P>?VVG3G86C0Df{NOS*JLqn=5~{JNuVcZt67~$MmkJ z0%WXx!zOdEXA`%w1n@nf@Egwz2^E%oTtd&9Qoj@?!xLBED6b|1+Rl%e(l-F|Pwu9W zp`zf*N>SoE_INCvy&)eWQOQ%KRU?wkWw3K)K~2XAQDk6X^M$7AhM+;c#0l}168g?Z zy>1sMDSRN@C;A&u?l28OK^ zWbQzDt)%V4Fe~gn+6S4h5co>cLpEBamZyw#tXD*@3Am0HBFab=q7fW-M^H7jwCQyz z&8#}c-s#~u8dh*~^Li;&b^7WK6aT~FYvg8w*)VXER6v-%(U!q)c;}!`qpNnI$~wOM zk%BI0S|IX-bfy5@!;@-Rf&6LNsGZ1a^GVcRti6qFNFFo9p-@>IVy@B)L);@=zr#te ztmvIqZwdoEps{>EVQ|#fh_HJkcgs=D3pEYm(_nfiXV2pHw(EPy(fnm*5;;kmf*nz# zX~RsxS*2JV8l{A|(Oc|b-`e1B)IY|9I9a?Uwv#cYEK`Q8V$)C37^+q3beYV+m>%h~m`S&;ly4&)7Z-xTaV1Kjg?|{zHo$vt)>r)D-6*?8- zVCEMB{~g_OgUA3V41&^)6U&I&3&Y6x?XjK3vaaK{v}5sgszlk4UzoLVau8-Srui?F zbZ%_4bp8lefQ%0)LEn?P_F*AThdI28e=jNYmj5j1FqyNXCx8u}v`l5AWpw@YU~?aD zp4_Z6d7_xk^F_igQUU`^%}hflpxh>%mwHb7g(iuaN-*AD;0s`>^~I^*-EZmLiNY)= zhV#I2pDGZ|S4kRG551t&J7}S+eirL^0`rNH1mJ`=z1Ly^PADTFopF~n*!2IZ?MdJ> zk9_odydoabtqY&OCRy7|seXHq`c^x4N4fZQN95OEXHV-is+J4tIrto9kt&Sas_GqE zBq?r(ol^qvtM{8oK0^KyK!P=v6Lh{UvCMp!W4mfXZ)};VnTe-^L8vEpP6Sz&FD)nG zLwY%ST|V(VZNDt*FY$2>tM^N$-(Z1*L+P`#w;ZHGs)(D@wpUI{V`$2H{R4@tk$}OCQg5+<1K|KAiR6$OVGR9=Np_n;p1#vG zbPqlBWa{U&Vd!z$1Rce|z$FP%nLtyOgii^lQI}8A9M=7S&c$|UPRzOhT&B_C-W(@+ z7ga0Lx!S?bg?{#%ydi*0!ICO?8vWck<3q z4Awd}{_*50U_Nv28v=lc>MoPe);g{8gbcof%6=K3yWI zVR_Q0fHtKEj~aL*`7X{l8d71v?PrRRUI0xMzOovl=Wl@&@dpclb4~ciU~N(dkQbOg zNZAA&3ykUQ;yd=e=&Uvb5|fV9oWdOYnhGY0w{EuMM3sf=qH_~;Y6S{D&eeYRBsMcm zgcLItIvq+7YpIp$QTyb2d#dyrG@P>d0-n@B8K7uPAc;L&Y@2g%v_>Hck83Vee-zOE zO7~YV81?EA*7%s!z%Ep#ss?k`Bk}P;xiaPHHeOjxNMGuAkeEXNN`Kqk^?(>Mj>(z? zL1WRuWmH+9{t*!Nd!99zEyGMVDB{0`MezfKD`-|$efL!j9pM*#o^n9rSMK@7L-{)Q zT}V;VYcdW-EEfI7)^iPevZ2WO^OesW>p0^_QFF+ZiD6if-(_l*$IK{do!@`8@q>kz zO-c57*kB;8bl6{lwxm9oYP=_~m1c=%`=Ocp(WTTAh(DjnZTDycj`q2PEH#F*@`j~T z>Ou$gCYW}|vg1D(UpQ?{XvH;aMD8ZV^~WpMVGNDArkDr}m%I(|k>SeFs=^JIcl(ofY}YqBaT7TZ`Vs&knX@ zl&xL8qJ~fJ-vLq(wTwXrS0Grn+b)?K9_xb&O9Qr))=(Iblc9~zm4K)q0wc%Uf-eq2 z%K$~gIK3Z?Y_v|G_36(a8p`IiLM;59CI&2n$q)8#@&g=NTk%=aECji0B2++%%;Gm- zEW_Sz%0FNHKFZqy4`xfBG5}9ThL6+B1SKH>lHV;-%hA51unn)X%e;1|o;+oOtv8xc z54Zgy|3`4@2~1-S7{Ti6DI9PXI{@M==L4E)xFKgXnjzyT)?kn^X>V^=0_Tg~x4eEX zABw>>YBDik75J$1_2Q81xIjVm92c}WtuLIq=x(*`6;ytSN+b{_*xqIJ3^4^{+Q2Xb zz+5cly20`pCQuh)9}O@nUyEt!31}SsX(<*2Hx1wg8i`Pp5V4UgSYqo@1k4>A*vG8Y zG5}689$*cxD>2dGThbS5Nv_=7l<0XqpI-7_k{0M~Hl+Mc$tnP$VLbU0HZU4BH9%{P zq1(1PSOQub>%Lph;~+-a%{B@#KYO$Lxds>K{NL7~OZNFIrNZGH2nH2ZiN4nBoHN;d zx6re*;ZMci1$c2DyNtRx;02+8p~H}2QZSxjyxgrK{Pc9O36^FDp=pNnBK?VtBmQs8 zc!P<)+>ka~j$n+#?1YgLA37+j32ZN5eGVY0l`|olK?KwucVJWM%HPYIy;?cg__)CH zi~i!~vi{oRaw`pAy0j1+f91f%v5M}H;w%W*31AmgmLuJN?sc z3jyuskE7Ut(<;7-13cIPbzo?lkZ;7mKY{1oV*qT|lgR*yatpZFJz(20Yw*uQ_yATU zjT)90VzFWZPQ#9G;r37Wgkdx=uPyGq19sRApzuL+d}@09+Ymrr0xpD1wGR)e2*(5e z^ovN{E&QJeLIOqA?rX58|L-dKN(|74VF0-CzojzpnFAlWCTPGA|FeL9#z_rW=L1h) z9+gwtRBH|a3cQdevJHT;MsQy+2bk_Xw zXUvx?$f*b@!36S_Q$_g6oVMZ7#C&bb%LY+=p83o$j7&_URkk|W@+ml&Gzt?EaYbSw zz%kENBk1YsxWeMbJMJIp;I>PiPVbFFg~q7 zNZez`AJ3H+v#_vu*puV8wGDuRE%ofjDVnLk1VXZ8TA%4>E_;0#IJmBHlHQV8WQQ|C zm8;OdSOkuJr~7IaBi|vrJiId2K}IKlH2zWo(b!}8$bpmt-TUew%pff+qK*hOl67wfn2e`c?+Fn#M5_Vg;`S8W1M|sKg=qSeh)nQ;zF?1aYK>(0-f00gB zr7Lq>b-`j%hOu92VDqL?0qR2RK}v>%|C}4FkqE%Z`kJo#`GJAZTLVdf2mYI4=|0egF0| zo;(59ma+_{hZYbd4AEy`{-XH>B@mNHHT0&nn|wTr^qL`~crV%#?3LOBJ>sLHP^gg! zLq~*#I4sA+r(?M8@8D30LXR4~vbIOqJnkNHK1hYXmo78egpx~HgSNM~FTZ}x2~4_7 z3N-u=wkrzT1KZ%0HL2Cv`<14<$@jkOyhqw*p^U4`ONHuPaMHTe>WvUQo7DvE5X`Ox z%-(cK%qwbd57yGJz{~hNee)C$_pbn{-cBgZD0C; z7@l;+8g|}=!>k+nK_Vh+z6Ik1?L}y1E6|_QR=^{O$KzAuLK@))m9L!B)~k}o3eXWE zemBI`)m1ii3nL@Qn5M;53u0791=5I9AO=O657YdXj{rFTZVvdY0JOE1Zydc^smrX> z1@USS9~w!%s?I8uC!1%7A&1orghq+J+AfKHHti>7J>FCSZ=8^KY2C56vgj{gUbCSj zjpenkS+29WO}s7AuB4x8k#c?nLfh_e{F}eS7`_LFp+pmAjKO8+U28d7$1O3rj3=h& zn&|68EGC>5o1T(iU0eImpTJV+!@~0Lj!fhOjAoM$&zD?42io^&<%Q z*hvKw8>{ocboiS9n@`7E7t+^=)kjvGf$=yZ2OHT!oj;kfl8ehL0{ zT{%gBM!XNEkP&k`oD0HDZ!+#`!`6PoYU)(h_$XSW*N6xWhrD8=NR%9egxmF*Qfj1i zP;C7K=QMPvIHk2x3mS_~8K%UbdCH2&v)l`q>YnGR)!&z|f%{>0QPi)G7DsEn-??Da zEBHPCbcH_O7>Gt_hcg`WU@6inm)yVG&=z@mxGl3=^s<@thq10cny5=z16{%jt~(rU zu+`MpTkDZBDCz+a?8>13x0_R#9}vpeiy<-Dh4bE}cde8yaeF+(4iRYN*w951%n%Ny zyouX052OD?El*(CyH{RK%I3Cb^KgeLoRSb zt*wxS8YPl7wou3^tZI4v#GSdci*uDA*-a;lg&H=y#X3`7VlF4ygM))o!&cGk;V&q@ zAu(bOZ$pQ)-&-05Fx#eNCCQ=yNfy!Za`Uv-ViBzwm<z3WgKA8t;+hWK%Y$;r_= zO+D2B^)ZK-h(vICs(7Uf?oWQ_*4!*L-?=#BH%K#kKSeV0O7 zpzkx$v`*GbKAN@(HjG(Pv>yae1k50ACGG;c3&V*qkX zjk{h=lxQTBr+cY9`FH*w2cZ;tW7PIhv%!PyOSVi%>$k7ySQjcKQGKz3Oj~kzQI&-n zZTvYN z@IyK0H53#S@{)nG7a4RaPZa09%pY#d$_^NH9nIr%Z?eqg7IDIA7DGhHA3s}UcRai6 z4t9LBPED4G&uzdEl3poO9FN<9><-zbyW7zERXIv`u9_^H@r{Ge;~ln<(H?1XF@ok# z-CBpkxi{c8EZHfW*KeP{a`Tz&|Ekrj^`VoDA+arOvRcDjJ~jeH98>mO{ly>jEU<|U zqFr{!jE0vQHLK4ds*d^Imv)8PH|rK?ba7w%{m>{Oi6#*!m^P0<(e7~S1S>5-ZyD`PRT7FL&D* zNWv8Gz7k(|ODnsgV?C82YkIt6snvpmZ(Utvg30L#Q@j@+8&0)lV*UOdde5}9rE}4` z%xP05Rmfl8To&c<@Q}^t#(~3r5)P;t0ky;g-u*&e>>T+t#E9I;?l#YIHj^&otdDP9 z=LktlOhFMbSzVleH}U%yU=wBJylUSvX6V$qvbYgHNU>ZNYUevs&=jR{5pNHtiUW{_ zfrXX5S1eOfURL(Gs{eQ_2MN_z9%i^-=wv|%IiuNpvFLG`ax#Cm^#1N8Il|XJs-fqy(c0=<_eV+$gwj5y!l4it9yQ!rEIO0vH~QwOkybX=I?XEM zjg}AsUJ#hVWpAZJd>jMhzug*u;`@h&f}4u){VXI}PESu;m#JjhZL@J79-0Z8Fh;*6 z@>{-SOy7;FEjKe9O64J_lsd@O$`nV4#NUS9XAELnmqf1;xDETXeE(&}&l@FAs(aPv znT%2F`8iFeCWs1Z47WAm0p*lkD_&@rIiU-#J%U=ii*%UP?f`GMzOto7IBJgCEp?&J zmXAMgsCD(LIY)umG*=Y<0vh>p^V@D7JQG|6z3cASJrfXn&ZwczUmW_Ui&m{b?tr(} zv>&Hpe?1zI)PA_ckTcUO)sO*v=|v3r(bzx&uY^t4FNal8EEXM%qXe-s;3nJ++#}bf z83xXWj_T`{+f@ur_&X>zB7Ah?bvi)1CsG=*m6tXEG_ZJ0GqvX zu{*xUfl}+d-F0<1U$)(2$v9t%5A@_&EtZr-jOR!P-7bAg+}RA9EY#TFXEW0RYRX!u ze0)IDJ2(^rcjMr=+oQ`9edim!xqb?H1#}j2;a#oL^B0@8*E!fuK@@9OwE!`4t%1^Z zu3}8NS1>TdXtG{J5QFj1R%g0u z$7L0z!hrUu)TAjgk*`PC)YK$nk4^X?)C7{(?I^Mv!O8IHFrE976R)*7L3YmH17f@W z{?o}|b1AaYv!PR2(Q8^$W}jXi+<@Ju_&GyMNGw@Pl<9@5ABK-mgj_Z)+0tK6&hyUh zh>3}Fv|1x)os3(4oZg%bzEgt>)H63RiR#S?UYlIMA8XxWtiu2ZPZXu~Un1H9185v; zB(XAa^hReJy~>I=r|TgX#8cPD%Q=IB0QhI8hc!z*ZHK0ZS$Den`T6z4Go@6-+5^oG z+^$bwE)0Y4GnR#3#5|=~2%#Y%0YU|+hOnd+8<4CLUw1+GrV;_b7DT(826R?ZiUlLj zG?~zhnGOfKNj*58Y_fcmK151zOK@o_$Cb7_{AikZC5tUlNzvlVIEzn?@a17?3+6|B zE1WC?&4=X(c#H8mY`f*AVh6v}`g*=#FJ!U9skD<`4I!ylnmStKB2e%-eK=$BjLovy z{*iu;V;w`9Y}T&{-u#FOK_jIvkxi}qZj1q+84?^^;8DrXVgmZQUGq!hFEap=nTY#0 zKwqW!{#0=e)ACn<$y}p2>XLI7p|`%`XOz1ml6zp;HD4=X-SwBa-7bA*Zu9p?OL%&E z^oF>Lxo8`#zpk{4`Gu&Q^A=322Sp*OHxH@?~pDOj8SJZH1s^ z6+0s%hbDiBp=-3(sprO_d91ZhFw0J=0sd8IFC68_Ct0G2caoqQJau^^Bg9jVdN>Ykj=(4ICbS0b#kICh5x6x!&n`{D zRdK2Vcgg^ZSaXM_Ydv4H^I7jd``Md7^PRf$5dxLE&;8(Tkg93n8~^EHIbCovW>tlj zd*{(UJBv>#72WaqT7Ac5z+mqCE90w?qjukH0YPl_=BDl%k+8kKG6Q_86!hc%3sJ+n z=Xf6|us%Q7uFX>^=PEJ@Kke}?Q7+-P7-O0wLlar&D7q88ww~{N@pglf%D<#x4D^69s zO&Gu@74j1w&6GG@>qJsZrrfs%bU}fJ1EN2k>^-w@tm(x zJIAl@w32aE3dMuV%YJ>Zj-#OcO2a;+Ul&fk-f;Sqb!I=QdYpE~zz|CxJ&P&1F7XL_ zK>_~3zxKTc_kobWhxPGs2R{$hIm|c$0^l)0-0}2zK|h z)|BDkWuo@EzL$E|SW>+UNe(MJ(jJl5t?mLg;S^@L{R#AN?}i;|n>HErBdETvuDNj1NNj$oaQ4hcl!`x%^V}bPvS-R;r#Mv9jAhjFEr+Ej48V) zW$l||Vz`b+-yF>v3a|!()v=$tLavWTN{z&H>n@hv;~2r0(f)JY^gSTX;3CIm>rdBj@NlI^u*Gv&ZmftX^vY{jg)1E7pbMyz)%d@d%3xeUSSd7swT&)m{4ZTP$~?^%|o z_Zd@OBd|eh@|(lC6l%RZUTzEmxD_g?U=d03K#3XX>n%jN_dhV>3|8%JE{KVURyH@O zY)?)%9?ikGze$^Sw{Po!r|sBypLd9E|*;1B#)cOC&&c@+d`o_3*4_+M7sHP7^J8#h6rfI3Rye z>%)@E-g|lW5Go_PF%YWoCVaKdJ zcX#s!=cpM(+Wg^7iDEk+d!F2frpvyT-XS>3#yqfyL@Q7oFXpO%)&YTZT~ORtT$eI(HRMGW`Zh9grM zmbZ>p4ADH&Mjtk#wK5I|a_x`|U+f{`*6iNAo$L@?3Ws6onVeT@$T^Qc@;Vr!2sYbsUw<3k6+ zA&}KUl9G}JS8FQbuT=Q1d;FJnnU|8$o5Y^SL8+W=p+wa9^kV;=u9U+MBeB%D99ZCp zMNeTrcLAluk$DrwZ~XrGDX5Z#IRNdlP_Cw45NJHTNm_L`!YViZDuosVNTqmurkqXi zJ5TpwI)sEZuKS?q;^ej2kdnmaW1=PhHxJeRNh(w#!*e;G21ZHNyQ11V!Z357o3;Ma zC;LCO78kCH=;&x}-#ZROEJoU(nn|gWJyU4|8z!9jr)j!mq`^My4laNH37~d%EaWq$ zN8>;JYN&&dD_bc1Sr2`%?y7T}#lZC`a)zF0sX5Q3LFCbG5#vJTT2?*C}6g;wpqQYEu*ZjN=Z=fP+$^>46iI*Q4bn3SNErGw7IF!{f6_Lb4uMpuMC|Yw)bZqe`f)%B z3f_D7ll?M_K~5647#2oR@^ndH)gVEp$}_n~UWfzz-Kuiq>xSSu@`X0Ua`*JkMGaEq z(MH-~^lowR_X8MP7Bx2u6=;-1BjGaRH4vYk1_1i7@q4P0x!ym^gmL2>3&!<)o!b_~Fg?-~Q?K8YB=e1t zZcC0R%HMvmHCqeGbrv?qbuwQYLC_>Pb01{H?Yvu{f2Ue+B_T(^hF4nAR&yzJ(s!M* zbdUWAIfQU9TCO1d5J!&9c{IZ?SQiB2BLV2emt*FE?ux@`??L12tc8CC)fNwcP}G%` zDCZzv9W90^*oir=?zv(y#gdjcNVd``Q4|TfVdvQiO%4W5{OIf(dwCSaK<0iIJ5ytV z25zHE*bMGO+)QxGP33oIt2*NOUQOi(T!3tg9VOAM^bENm@{Of{Jp*4!c=hfZazC0u z8*oGa@-$I%-=(xtq`ogm>8@k|psOzzx;-g5UcnS@%XhZ;`Igx)3kFuxH3e%R?@)WIBE{jK54kQ5RcT8QSJMy7)Z<2O#oj8z;>_Xh%nMbF&F_K6~L zHDf7cTV)Ic+hR8VPTm2XGRZ*#)p}hRbvEls_e7c_evj-qc`MyGpb4KbnU_RL;amGa zC&R^Sq&PlrbwDu0!5_rB&nREv4*@1mwu5f4$f zU%T7px=_L2W)UYZkk! zxBn&=N8X-&M0ja<)iH|B9uBk?q8Eh_qoqyBDpX|8qbmJj>b;(qa&ynoc^OFXBak z3IUE0Ii8>+Jy3zq-z%m!*j_P=A#bdwkcuDJ;agCdTM)-J)olLOlf=Y+hkX9=*?rNz z>kDfDG>$xe%vuoATF<4_jLROJ-@|Jg?q5JdcC`NwWimeCjto@wXMLF%^S8+}_z`(t zt?j<73Cdk(PHZi^Ys>so|EUp61CoHHAfxf_1|SliKIuRJUF;;*5^w0^+Xqf5jf{-K zPE(>+R#Tpbi!7F)E{_<2ugUSMj}`Ioz_Y5{0hzNH6lw&7YeXb5mL1r4bXaQpqS3^o zxY;ct0|L6#HYAD1h3fTkR#iylV#}B47OvCm>ysT2=KS7*f7-c$FeLzvpQsD9&gK_8 z<7kKXb(FNUSLHoL#l<5T5|}mDN8!^=%dGatJPi-;Zx&G)uzP}WHW4XS>V@i12?A2w zU7wlMx?C*T{D0A)rt=5Z_N!BJYnF@UF(fbr0iCoa6C3zrW+ZpCK+BR0{Fi0VTS%m< zw2)=A#dJDcjru~IzUVFBb*!uq0&p>H0533Z>;yzr0FVL+HJIB52T?>sL>vX$4*Z58 z%-zI>{(kq#0B|Q!p}Y*Iob*}0Ie>hfTGqTcU%6v-T5pb{y@ezVi6gcqcbN1xt@@mB8_}F zk8+s8)l~>6jMmp$K4(aZ%^2DX;3e{*MLhPJbcH$NvY9Tutn^*?`cxT*&0A$fUwJraE_7qV7dHG9j53xP z)#;QSVm+Bz`U0u5Wfao^Xw|MsCfU5UP5J-Wd(WV#zNK$eMly&Lv54Y-8Jyr98 zFnjM_-MxMtR(EeP`EflZ-R5c{*>EGilZwNLfDj=t@qtlVEdQS~$te)r{0M(!*5no! zw;=2Ey+XY6?`ef)BrWB-jmMG zT!r@&(xdWylr8?QpiDkEOT9%|zdIo1_{-&o_R-9~vHTYG@V5b1s-zO3kNYG%mi)`F!t5Qz7op&8 zCyLK!)X9q>iLX?u%O^M)tX_#&dab{P1-lX@?iWNdoQYabQ0q7BU%etfXqQQod`cLt z(W@-D&_J!OA>nHYnhatj()*c$Gl!>qmq`2&T~!+@y{3gF9elW=?;_q;q}#58NbiF^ z8zlUf$JrH-s7-&%()DpDszCJI)Xx(_a{>WXbWsPM|IkYV=;ilt-D8+6R!T?}3g#!K z5xs7tk3VAQz9#Y&eVxRC%slao7E)_DWL;5W1aKZrvp-=05YB zYX0xWDtaT9%3d=I_kbhiQS$qoy5%|d&CUSo`!3s>H2yH#P6h07Zk+let<~;;OAO_8 zYAqix1(Y5xS=HG$T2mYUfxEL^Xk+I*8qhABci@8Zn zO8;&_J7vai0XS0~mg*()KOaoBUI{8|7ewBZL>uJ*kgZ~=vnm1|Az8%;& zqeWv5ckA~4VRA3(TLIRNsW%9SjHpUK|<5qsM3ce|?=~7`5u0SnpC~?hPbaPjNVOSzbTLa$58kTM}Du1BPvg z{aWaxEC@f3Zw*FaE{x|H)kr7Xm@>8ENiDejl@XvVCx1?w$#3R|{~fH5+%-Y$u+!%t zH|{4Dl^IraW}=nrGlVSM&72Z63sp4Y$<2r z_wU{7ejx)b!rsi9*0xw_*8QmoUF`;GGa?VUfY?mSvn7>OzX5i|@V8MN`KS#F#|~fD zNeE~R?jB097V)m?ysP47;-*hNcR@tlGoer*>hx zDDFTIfpR56X5~SzCaag%bGvSAxMntKG69*Y%mpJ&><$*z2_q@KTzC)vS*nm|n=+Wc z0aE+=mu4X4T;!Qx^h+#&HSF@id9(hVs(0okg#9GjL_|Dv!_(eW{#JLzQo+c@8$G#( zFTe~==Y%V@2CD4ME*0NOUCf!$VI_bj|$Ez;uI> z0EwwtyFruNC!K+D{FWb?nBFZ{~TL~vadDLf8sy7U1&F|azdmC*xyD~Jw^Isfn{IvU0KF-2 z`yuW|_p!TI=Lad0h`mcQ+?D{j=bJZhh}m|f-bY_G^Lc^3m?HZ$1x^d58lTI=hV*o8 zy8V&Sd(_od10V95LA$gl7!9FQcGw^c3R|T?99Ep{U2p%T)^l08{bHYi+ySKmnj6&G z@j<@+h52zZJ|O`ZhFG?@GVd7G#AEz%1@5ZD*cFERWHZC`x~cYI;l0_O-nRl{(lHuW zx;}>hV}dS(x`{(xSVf1Jerxi260(2ThptP9@BQ{o-*q-9lQa`APjWG=#Ps0xpcd|u zgF1)ozae5cnpWtp#v^MVB9*_5&R@Z}fFCuclsVrk#oFFh^2gJ*GMwGk2w%68ap8q#cx)UWcId$7MLgWa4_mo0VBC@pc8q@To?T>scnJ znP8V%qtWo^5z~)%scUlj!?7L^P>6xUSsiZVjpOjl;T#dW$!(eJ&Hzen%N92;SmG4( zLJ7>j5d^h6T{t!q)gl20)1$Aqcnige%#8oig)MffX=uV0I$&SdRKl`PUWEKS(d9I6 z$G^7qhaA<*0|GrG*=DihVy54V!=vg?_7C$Oh_qgPCMXH){ewoBHrSw^r~R6*mZPA-@t z)eH|q&znUW9`Xqlas|1sPACJTQOg+xFxS08wEIW63hRye+44;AT zBNKnlBpe+wr=Lbv1RtY1x&jMB33g=mF79E5t`Jw>R!*RtWK5CIIOHP_dgU_;twDui za)H*sS6vvgs5{@XNKtOW_EY5}+@)=g{MzMRwRTavw0GZ`Q9Zt{K{DG;rg$pJ3@QU{ zm-fY92H^Z|U zJxKzAxp@+}#NN^m0ipHq#fq8m+WaqWtG|{^KKR_hz0LT(mHELZ)SAnvKDt^t_9`FS zM+2?r$z#BYHF_LQ9J;+upzFz5cp=~vr_t>@X~N}vLe1r+iea1v@5=~|w)xoH z3a;!wtNsE0-wmcHUw)pInonFBC6=!%RucT%CReAwn($HSXO(r?&vIRUTI;YY@_y|T z=w;ThHiy{L9D;7x`&|Udk2gkpO31gMJVIbn4dV-y?X8IJy}E#-)w=A9Gi!KjyMGC;T)xHv z>A3i8liLe5SW@KpHj-!YJh@T-x6xu^$_E?yNvitaD=%NUeueGAO{#vong;)2z zDY46q&ef(WcM6OxRjeV;$aN-Gx%%*n z!;eYA2Y#0dLA6>=`=_+}9Jc4KcDt)t)-GNlOT_UIY7&`F4Tu>#`jz;7k9wWBF)p0# zPAS-cYfp^)Ink>$S&ZO8L{SZ9v~MW!fjjCIHt}O*EX4S&Chagnf?Q+5qg%x~q@+^x zYrAJ?`^8`ar*83cih}68qX3j&<)1s$p>kh4?}MCVOr$_(e7KkgN$c7m<>2!2UWrbt zEzYpj2THjr{EjLe+%!_hPoY&CMV9k-O&Y?;3+_pf#E{&(Rm!E+$>Mi6>Fzm&q>odI zR-Tx%S7H$fAe>vGQw5UChOQD`=P|(S$L0+!Av^s4AWw{Aoc&hRZx63~743)!*zU!T ztNZsIsYPHvPynPKV-f_4?bbYg$*nxKr1JXM6l548y5~;8V?<$n-bzIzwx#R);X{ov z!|g^_&Qarp4IBk75|(PeY%%9&?`vB_&agBKy41({ifr+;Ym5%z<|VFm&KrKxvH{r1 z`Ope(l~SpO?2&-bUYl$Nqq*Rnj*m0yYLWUuhqa+(Isq4L8Kl>v!znn3eSi>Xs=niLhSMMwbO_SO2o5iyRHZ z5oW@`w$d=IKQ8+BoMTcj6l59Wi3H({2Vd4CCP?f81JKVMul%l$|h=W4yHioAiH$O=Ij@}Vf zKy;aSnDI|7K>5|TM}=v|$D5i`aRd#S%$9vbgMI699$GLY1E0&X&CC<&{XaUJa&H)$ zBW@_26q(O7b$eehDhyHa!T*%YHpS|pncG#T=m{uju)5mjy;ep%RHA>>pKO&m?_1G^ z@sp97TYr?<%`VQWe<==3{U>t4m&jW*f;Vm!&_Cb4eUyF`>-c=MLq^iBx8bJe{n^Od zYrH-DlsnoyFG*vrjplD-jlScq;l5b$g$_)I^PZ>YudBtgZ&zKR@Xbw}#DXJg)KmM) zyKmo48eDHgm7snc;vnb7`|-g9+dIi&1k~CLDmx1uY57(B-fR%fNd0Hrv#eMj2u{A_ zwXh?(gkgSCwsZW$SEs=i;aayos8CQ|Tr}Cl%{lbmCP*{5 zR8DME+Vs3KDl1=@+Bo75SgIKC#JEYy=C;wdrQmkv3(dVM)N2>~>+ccy?x{K^CdXUo z_tx$Gc@k%_oLzK>Kf3iY1%r%hIDw!uWh98^DPF5)!!G6CS*1 z7ocbi*&RF~I0^bIV6O;aeXS2sCWR*Sfo22hlCX}yl1asmX125T78bmUSgAvy+K3^f zxg1~j6Ux_;FuY<9i|nf~*5hwvqoP;^A~43lhNX$*Ey2Sc{}0juBZ*aGp|@iU zlr%-i(ekPh*!&tue2DRT6QRbBts-O^S!0Llu?b0imX@$bljbwlo&{|w1>JZJnM*&S zHOB*fXli{r@Nat9O^M1a)Ee#PI54t3h_ri`q~YTe+V}leu&e$Ximr3r_*brz%MGwF z`JlH!!v@jlE#8eeT3&=Wxg!d}6}!zc!GXhX%Q;O-P_BhE_7wL2M0OuW;8|y~cN~(T z&R=o#y0~6WH*FskbEdL7CE)~6frni18Il0P-hg9B_u=~mLm~6o5vn}vYwM#t9^6aK zAS$cBUb6i5riN#1FD>2+E`+=x>h=55elKP$`b$?;cYW2FAzjL^a+e%d>2OE$SYE)s zE4Op%&_af{c|`33;*a-I=2iSXl>2kY-}TGKp*Z2{I#2UhU*lq36|)H+{7lUe-xCe$ z6_oy4xZskgARbCgcShA&C}Avqu%rn1D2<)S47aT=t+a~4lzcctNtFZ7^ zVD`C0Wpnhbw5E`dSEWM*Cc0)ObK0$YYs9t4YyAz6EKht;zD&(_V0$HrYy_U{$)?8n z2m8PqkAoQNo;onO&q|&izj3;XvCK0XEMs^^}z+#G@fgzIBI(w`cm8N)aD`S%c;LT00(YLLF*|;$@ zznS`S8Y)9A_{C8U_D}lv!|NQ@?n5oND?7ZVulVa>nBpB-ce>ARHB)jw42a zEBAk-IBmR$FMWFFWpOmdn*g&qLs3t?!SS-_sA~N9MKWW`J;za^fB=r;o$l!pJV=<; z&}}_*BRxZM681h@>hWA&nN3`(c-ee>tK3CI|KWzqi}?wLw%U;KT#chV4mP$TrLq3p zcd4i2DP8DsaSo>!40sB_&6dStAwYa_Q=WGHT@2sjakSv)fGgOGZA(Gg9(5fLtTq?- z)4C{QSCY&@@^{2ZE zzR7q!n{I^xK_JZBHVr$6Z7%$rS36&PLQX7xXn=0KJe5dS5VBV5V0YC|N&4#J;cgLf z{eClVD8ts!=`=A>+>|cP>pHe$hdiKrPT8*vhxO zf3<1y-o&%fb&_gprm0Uxrj7a})Ysjk>vl{EqkztfO$4?olh?HodS;gPh9aZNl;d>+ zJWvEK)^>ALJE!FDynR}NpA%nSu9n_Q7i0c}YOR_qW=C(R=dTrsO;xE#Ya=)17vqcu z%L;_R#Y#vzOJ?Tz>^~wTWgfyNSK;0Et#UFn;G_r-_*FfSoB@Bi z(f(b}9SYUO%?z;Gee6Vb_%oYmy1OhzQ{x&l`e=Sj81vZJTO-59cb2!FrpFhlhP=;M zTAe*wK0cK4T=|1zZ|45QJ!Tm8iaI_)Dd$bGlb;rwkF!_)Ik_JG;WrWLT0i4!e;Hy7 z4Hjg%($E;E>bV0o_hTo4&EAL+)fgAznWn|jR{Q9dhs~@h|2gkOzlQ}@P|Vg@l`ih@ zlv2YHPO-#TvoIIg*b~sQNN&>RkknGDV)0{nT)eP1@KEdW)AAjiJ&yEq(;Tfos+Jzh z-X-p5N7>SQp&8n0oqw5_Po9shE4E2(52V?Bvo7U+W->)VpvZ^CLWtlXGNuy}%IXr( z!p?yBpFZV7)f)P4cd&Xg+$?ea^ z2iccD5|-M;3#mP%sOS7U6SUhhjC=QjJQ0FqRf7?qN9SCeJJkn- z73UKi{++Zf^PD+|#L+TM<&}uDoQS9c_aTkNuQ^@gu%okAxV>YIRk@b7o^y zw=NoD?afR#S)(uVCQ~!RoVV|uzca@uXh?B|qgNyX`$rBOr^n@r6`sZwM}Ms}lo@TU zruoeG60`7eT|>-v9QDW;)@$?lIX4_LJo_o!xF@r;MkI;?8g&6d`;=rwsQ5i^j}w4> zBdbA{X7WYd;-RXPZ2iQd{ABRQ=p8%Zz)suVW(Bk+V<>s}$GDXD*<78zU#bfA<}4Nj zgzoKyHV=WfDD+UTVf^e)V($c8svT}SONAcs=Fa<-y_24%1@4G2b|NSInZ2INQ&!{m zVM^r`E$B%EG~KA#MdHDGD@MbijOTrB-E|GrwI0V$-p_i|NZl@c-Xg}dqjG-JaaE7) z>z|qU7Isk_3J4$HG|Uk82>=1 zYD}ustv8=ztIA7&>E$gn4YY=I3)t!Fm{9vbE`79uwLeulK_B73@~5L)x-~A`z4@-i z3M;3YJZ~>MajzJm+)CO8FW(!95#0PZ7mHCMd$SwoQm8P;jxtSVapYg&2`Ng^EQGya zhVz%*#1pH2){|K!#Yq93kIo?f&JuZE537gAop?>vJvsC;(BvyS`5_`K^6g?lcBmcsI3{G}7Eej1M)Q32im(FFyJDAPSUkR&T>0obnSJ**29VmJ2q;alx!`v&osnqZPqJCM5384<#;D$QYBd@q|?-N+R4lH zv5C7_-S%gsq#GMVC0Z|ZYoQD|FTiVFBd_m(?HS?zCfVf+u zvNHHR1$1wqZ$>){Ufd}eSbZs87pkXjzJOsn#YfPf`v%iHU2>_Lbq9Iwyg^Qv4!8Cl zKNUlHd!Vc_4kPEZ?q*`fA*d_bUHZ%#)3N}$&4_<#6cu#u%v-(mE~jo2GmdsETPAF% zj!NXNfkt0{hf1pZ?JZ&WtC;ue+s`PWx{WKyw}v0zNw42yB1F8+;=xmaV2&AKOSfUG zYIVNfkJ;=UCmxS^>L0Mj7fS}5;G6kx6O;|{iNHGD(_2M6!ai^a@W2_94(6E9ZOTY} z!?A`WJ$uY^K<(#Nddz#oGFcptaW(Hmjaj4u!C*nm*{=iG?$JOY*I;H+`V9h34dhn{ z-e>iURaeo$V_TR5I>R#9O~woR8D-0(yh&!M3yT%p*L9hk@Vi9#F^?dv1Sjr=6vv$s zZov08TY^um`1LfX+~y|P`g9q^#Q*y!KRA7s-MikSbJ-)XYRjue%Hz{~3W-7EZ82?tr(kRp5#qJ^QSL|1 z+zc-QCqhKMYf6<$vb-Q9%lDG_d(-dWV`3bBFV_UVH2D>Rl^I#*m>za)ZMVY4LG=uG z^XC4?i=niqVHai=bq);Mp>Mi44%i8^RuH~KMsxclA}~+#9h_B(S~^fmv;-fZ}5K1;F59sst8%p zLON||f(@aB*0rtmb}~T2rTN}pHeL$jXm8xXoDgdLxxX<=eCtHzhx!0wdCu0ls3+`TLWYRe48g1hoj{aY4>bf zZnj{~m~V0jd!B>*m?8%ju$~@IU4AIktq4=J{IckOanJWPgyXWn2Y%oJ^|gvfggBm9 zlVQKMgq+S@u-C&RW;wP_Y`TMgd8c?$Yu}R$!LIr^U-G*B^p~|Vru{nrG(;SE4m8jh zu~xi8rp8w5IK*zkOV!s64OrPYao&c-e;9oMaxEEbAo0J;_cG&Iy{OBOlvL=w|7Z+^ zvPB>77(Kicnzmt5!l#RVaF>Q80Xrnil<3Lc(~^}VHP7R`d3K`kbICL6MyKzpLMG(0 z7xX(?x$ks-^6k`KuxeXuZt*xV*JzRYFHB1ta+mgg$;fv@udT}!hi=eO+6nF*)a;=K z^eJ2mZ~d3Ht>-p%q(z5+S&r<084DfE%@*xK|W(_LZCD7k&*YX7YdijiYh znowr4UDDnB@B^blpP8y!^M?DKlxvHAAd^|rm+K`n9(4Y)5u9@vO6VS4Lrr;H`{rz3bnx$rGQr=a)D(1+?Ojw)&`&}Nm?wmThg#o;NpoFep zTU5kFIcF)!pcv%dPh~gMSC!BIHfo*kKXB~$zW*~X9=1_F8(i01mL11&;55>@JDU~l z`t|jFs!K5yAriV~w}JfI$HwnN#WeqG0}&$bEoRdgv=rSZi)YsZMEJSMbYoYu!!bMf zG0m_DJ`tI28P^60LWJ0Iw=Qi#grii^lq^>>*-r|=JqtA7wknTa3;y>bH{-+~AQ<^t z^>`7n0ioF7?0Rug3HzDb1v~rI*8qVM`Te1pSy(WyJDL^*yQLoNmex}$ z9zzlkHgbf+9C5X$KMYK;sGwaSSz*6vlDB8*TS-J0$Z)imu`s)d>P*KMuma+Y#Z&Q^ zn`%G-{b#%4v2u?m6^Lp6z8jntU-a3>e_yVeQwb^KbYhOrX;~qXYIf7xl#EOq*8Blm z)Dl9uWdr;*g;2E2h46V2sCIP9V#o-sx#1)n{b5(sAb@^#i_n}Sl8FF6lI~ci%mQ|c zL55KdRlt#AKtI<=*7Sau$vkOcfD8VOPAb`A5__&*Cx(>56xr(#C1Z?5jyG7AIgsgF zNl_wbcO*?rd#H>uCFqm2QVWI+sH|dUWwyt%rNx3zN!m_uA>Le|8Rw zd}LdO`+YwiYZ|N#e#^(Xnurryj@$8@BD2X{{4+OzL)aY_yO436x?^B zr4=caW5xw;u6Y%MCc3FwFK6t(gI#g=953RF?D{JMj`((<=WG+rNe}#~zWdi5Pg;k@ zc{KYOBq|M6AESG}=>OlIUIkoKHXegVliJOgbpO7c1LMEtzL%!0qNM6W4h?EnMd^!c zQ`0{0rvpi~P47sYr+nrY?Rd-lGJ(4DjZm7Um!#5C_3l|5ii%%uZ!IRJw^>eeV44Nb zW@s=LwH^(1A+(4^pWG$jR7bzuOdoHE{{DMNmLRe+@m6IfmsldLcUVuVZVk`GEk+TQ+l_*?76iQ;U*G0WEHZ={PqU4iw-QHy|3Gln*>QDvSe#3ntC zVEc674iGq-mnz#8GcNbr#A06qWXAX?RYU`w0_p_M42v0(#LY3 zUw2L;&sB_fwwZOE@9tX}XSFk{E_k|*JhKdW>e%Bk^m6U$mRq$kF*T0%hwq>Zc1fUFSlHU?a*>LjIEu(y3p)4(h+2U-Z8`|boy)Jn{Uh*?iC&-*$AzH*HpNlU7cNW1m3PN zGe41;JP1IcgR6zf#b?JZ68FKLc@dRXi@?MDms%0~+ii%pT`$z9 zz{}m&NUjg0)BFUAa#(~G6ummJITR1}%Z>N@*X0w3l4)Rdl{%dGac*P9T(b1XgtiB1Eq~nTWV1Vn%}MBq zN&~NBkQ9H9R~%FM5HW5J8nCIG_kIyDuL(rq0SNm5k|2D1j|@IxVzIm^C{`wQ|IxM;}OC zYBwyaz9s5hb{6vE8J;*0bWEu6UVx4E*pDhE4y~VhNnza&!<=bLX&$*{)0^&_Ck(x? z%AZ|+#lfhEIQ`D|x{Ti*Q_{*)%d(}2B&VqQ@y>$kLP}iao5Eh}LW88m$Q3Z5;PgdS7QJIN;)_<*If zKD8snlmhjFGC#@;!BTCAW@VMYJu{Tv70%vapAK_T3 z5N3{d>pmP~ecBrqnALo@Y@V=9oQ_M_%QgWQC%G#C*sp5E2uRt^VOnZx1c0d`F#aOo z$&^NI#=FE19P|OsO1MV||882hz1z)1Lc$3g&r#`p4alUX$yYs(^VOL?c~$i0Ymauv zG-N$lTS~|JRBc$1INVMtfJog46q=}W{Vw!#ClubjGJ=SEpR=Z+YF=B2E%LQUf!@l+ zf`*c?wa#_kE0JD;?0TfaQcukN(c(RFUtoZAsf9_VNU`4_SG|uk+!%aH;AqQ+}$8*5F2U?sqQ3;;*E!6~c+w5JAO;yAvRd)m<_^Ca9Bo2}b> z^Joykr)eDc>mzpi;zyeJek*QIJZ(f@a2M~VxC7(r7uja@(1LAG@Lgl#i9^lPZe!z(_-;28dvx#J~WH1QQr^bJ+?JHUQpr8j4IdiwfL zkJkI?V2XO{UNjvG$hjv?~C?%oRAhS@=<=RQHf^iDNgARR`GrPuY){A{!S*V)C_uTPB*9& zxCS4Lh^OV}2NOp&4Xrf-X_j5kcs^HV1*Z2IGLYLVs7Xh1^)l3>=mAE643&co0Y;f9 zG5t<~x|-U%}kKsAu-@;$nvP+v)lICXAGl?GD z=)Bw~dT-fA1=_6?jWWxu!~OuFmn}|tIegQMfT%?(OGhznu)+EL~bczPq-j&>r zU$%dK`?R-ni!gaolx^E;$&U$BX|_~w{?2%y_GDwSrf`n=OG+JhP9h}?C1wVG;Dmpx zHm^lbF=NZ{=~}wv+`) z%X%0S#|5%#yIom#y35&)I6P>5Yg=tTl{8? zoQiwoo>tX+N$&6mQn-&YOP5m~`Po~s^Kl5JQ;1(Se;T%pW;jW-b-&IOD$s4bTO;{p;eMeScQQEDwj< zaQ9MT0(Dc|Rz{M#>8@)tnW}b6qhx6dH~RGH`hG|IJzb^!Y24_qVxlCO2P(T?@OG9a zt;72kI)Y78duw@8XrV6(u!~c!*-RZ;naRSA_3z!RD^acxO;`lX*{_Kzy5AF3334ck zzDX&!{r7Wwyjkr%Rl%>InjY5lrG%ClEk12@%M%VZ!!w!^uml25<0;nYoo^e)w zC#uY2U|knfi=oF1^vR?G?-JDC(fe>>XGao6-(9?qRSG}@oIuqGH9dyl0vU^_zBWWS zgoLzHw1BZG=(>S37WTs+H|T2b8C^r|YT`}4!G7BrzBo?HDeU_&4E=m>cmpX6_wJis zTlsv2@;o<%{%zlrlaiF$Cq1}s3+=RiZI*qewUtinK!1Mz^4$CjV%8Gmy1%ct1M6#U zpx^IWZcUqZ1RVDqGG;2e*G;CtvtvY_LrT+Xz31 zEhwLP%|`F3RJ)!*ek7 z{uYEbl)(?`W(0jdy>Gr}*F3~^+Yn~*L9@FyRAuszesB*Ft6>Q{vaeEr z=?g4fA5#YZsRbB5RlxVH4hxCG_O$TrdM zSz^!VN;OK+AD%>gd;3&>T=`S-50>4t?ot}rZFhvEw^4`oG&y)CKaev&J{c2M2k`sX z`HB%KxCAG{9t*Ii(Q4wq=iq#x15qnOmACqnkhf|YGyHsoOZv}>&E{N#fSy)812`sT z0yzCFqE7{+VTzyKz~Sfa?tQ2%fMxygWkMfRQSBTh-rN&YlM=t+?78yr&gUkxZ#$<; zCLdkgb$0fifa`aGvUWRurUIJK2oSfdDggchV%C?++!9{5QfK{#7JkH3S zOlobZG_$u4E>BLSD7Y-9;HuCkpVePAAO00U<#)-`16=36j(f0k=dTOBl)=63y7<}C zKrZ)xdUdmb9Db_xPKPY6zu$&E4F%#?DK>k!=&hP?!8j*CwgIzzq&~rew)-H_k_2>B z^vC9B8tit*dBvZ6Dhs_hKl}`;EC>?9E^yfO6NPl(nWto=$6r2lbTkk5?>zzpO9O)U z6Q^&21jR?7%fI&>U?17hJSJd@;Of5pJUwHex1<}5_-{jLpT+r)?8q-T%IK{WxgM3m zaPq01ka%!L#|sppc6aY7kmRjvKS+?1UM~Zbo^Y&YsVqoiK!TgSORUB+@;lcTT}cTN zsh8LwrG(=zCn@++tDX;tVdy%_a0aXYb-e`Goz)P+*a(g@_IH}`Rq3*x-T|vyFE*I56~ma==5~JgB$XZ%fd{Ns-(M57cKaq`3vsp zemwMIkB_R>w&sOSqx@OEkj2j7)R)KLpRGDIsAa1woM=YvdpP4)X6hvb+1Og6> z1C=sRL6ID)AdLg}G1KBPe6u{BVgQwh>Hb0r!haK7XyuC#<{+i+J^s`?+C_32+Nlao zu4zDUXoEPR0Q=$(V29G<_fWkmvR#it3TodrdJ?Gp&k@41;qK`b8F)`j*KW?|L-~OOb-=0>*anVOks9)Zh+~XL-BV$Xn~u$c2QZ8o zxL=kAKnrL!0hvw=kT3&kGa7JB2K?#Jq=CbdtR>(oZ*AY>K71O~PLlc~TLFNs79-b% zkCwDR0UV=Trxqj2)H7OMY}05|6oOd;dHGwG zSho`(#z-ui;}_TFRI^+{mrRft@Z*WXNF4VgrW)k*n|6Y@bV|-XDs^!Y_+V|ywh>5j zF~}v$1_4Yd<}jk?0+O%+J0j8(qt6Pb1+?-feFgthSnh9DWikN_vx(cgmUtDgeaRgw9q6xO_83G|ElwhMFGb6FxqIRU=stBa74cL9!vwicLJ*x%Lk=M5xqu< z$!Fkq9@W2_d4MGZ;4y6F^;$OpBG7V9-O1<1;V*_x(-2sIENXH>)PJ`)!eBjvNZ&CA z9BuKU6~zbI06F$ZMPXN*y z8Sf`QUIzcYdpp%3MkR90%?T=)e+|dHuMs4`%yGgkuvtl|z*QzE#OJ~|DCDQLABI5k z`jp~QEC6&Ofjn_OZz_y}n*=3Szw|Caa;asxrz4fF1&;gQ(R~nnh5P2DmOnW6d;XIA z+rJ#^-X$PWY%Q?u9td~JLoUIWEN&M85~BN)U}^HT;P+2t%LiapW}w7=Ea*8@HtCw< z((<_sj5yj2MN3GZYtU?n@R9^y;DftA@OG0f!?0KWA}5HvCqqd;nPI(w@lnECqDAzd*z| z3-Hx)VKWJk69^eba*bxo`~uF3_T-Ww+=~K=vsCX)whR1;WiKXB0Y(ROVS#z@v>b#@ zWy0`zA0SFCPKi7J<`g|6Q0A}T2@hG8iCEDf3LH2jy$giuGHB5=;VYR{`r}lw0o9iWKqKl<@Wi8{o(4$O0fIg9G?( zzzjxJU+-52D;#jFt53?G$Brcj7{ZVDKWhBHMT-AX@F@(H@+B)RUBivg$fo_}H zL)Z(a_T22+7wSWgR;EuD-^9>Hm~ zkK)h=X;v};5|NJpONyn!=w%2}k)?o{7L=@{>{w}*%lBCU$F#jZQFYzQqdhgMn&NGtk2ZE8KWm*Hjn&lKViJKqa4WNbp z<_73lfuCo29l9t0MiV;t6}w*F2Tl;_9qSD@nySLw#quOUkz{>Fwpx%qbUEEO`4E5= zPtEhrl@(!eiH$0dN-^IKBn9>CH^I8t{}cBR0Kek8DuBD&!B~Mm$19kvpQL zt@Y4~_sP?W4jh)u8lB=y<-%fGT1@gGOO5+1sqP;z+bRV}aH=XZEe_mI!v9bPsybgY z0~Ls`oqlCS5FiYfSVP;hsTU%r(G<~|2nDpXDRi&(pt?=)u;loV@cF^uBl*v>;1$Qu zm3WNq_uKD#6s}OI^HY%NL;*u_YjE@0htdMF6?JY9m?!(}OQP*j^2e~Gv3r%&6zWN` zZA(SsA)fodJ$`*LESfB?&vz)A{AO|?G+Oqeptm{{kJL_#g}t8x{^mM*!UdGw&6vgm z%gzPMcKPmL{g(|rugp~sXI^FpwVzFTGiaZwl?in){ya7h##0)hTjBLI1iEC|=sa!F z@d`~Np~$KL-=o(Ys;vGxBz0}1nyDUgC|WeQV8`<6!|1> zKzz;=x^{M@xqugu3Y}yl?$41@|}dJmvfz<*c04mqAN&oW^>L}o>M2k$&5H-*-GCW!Nn zw`I5GDIM}=MCp(9dUr(Z=oi^4{rR=PF#gU+sUpLkHH$ZH%7jYl+diaWb_*K#`++t> zI*y^u@5%Q0qUYaX)07sqzy>61Jq*-O4tF|BAn5Z*u8k~>fp#1=F>qQPcX5#GDxhJE z2=6|8A)!O*r)iJ#sb|DDC8Tkh zo9F$cORre62zyZI|Uqqh_j99JryV?pojLiX9{dD+>U+jF9?OV7wQ?ue%a ziV%60v^&c*(HDk+`~IObM}e1Gs)H5$`0Twfc!$)StM97yoK0Ib?~zAEhT~F*IoGP6 zdQQ`U+b07L12+i>wlu*5XV^aXjX*X~G8_1Gq}fu>VL@}Dn2o(CwEdV5ax#rg@En{R zQIm;vUbhfj2bQ<+Vjd;fLN3WTP;GIVh~r||V&PUi?B(&8))bh+nDV!QYid#@IL z4wte=UOOk;q2_a3;ei#keBhV^rv&#SfQGc(a|m7n8jvMHkv{p!O7uL!7-R1+vU;SR z2feqJCLTz_U-p_n1dc;i!X!B&(aPSeZ8aymE_KCTs^wOa;fRr@6?a&8c4JsYP5wTh zDjHdD3K$FlE+~)SpUo*6?2u@!cE@E!F%*Y;=T^Fee@`H)d$mf_nOOUqpa!URLu{o3 zO){PciPk*l(W^<~6A+4m-S{1Zy9v^X(X6CcMT4#J!j{odzNTtuv9 zSw6XyPAh%x8J)y=pZfy|a|0^%K$|S%x8)dY)z@es6GB;B`sQ;vzXdtuF4(`34oZ*D!Cgsvk^+mx?&r>mPx6qzG%D~|jaaoaok$nXrGa}4xC`aiQ zmYMmF=iHO>TyEcJ#gk{Q125$S=$Z!P?{)AJo+$AMxRTc&K@f={o;(_jOONmX#QM;g zX(wU>PxQY&kO2_Gu)Y=teC|-nH~@)_e_iw$M6=7b|J~sS#`0eu;8){YwzE0@3r?@d$09fzxF@7UP#>5hx5XOFR@#IZ!E0{GX}$Tu1e^V zSbg*gIOq9OKK@JWhJnr22EcYxq`nf;M?rz0NcFYP=R4!Ds62=2rRF1Fm+?F9o$+K6qtw`Cp_EktNhJ}B-|6B zeeUsW^Y!6u=R!aPfeM5QB;&Tdz)U7$g6k$e*C)2je|x*GrT@6eX)*kiS-W7y);EP& z5ZwyVST2_dB1iai9NQ2+TVmPdmt}9)4&W3MVRCCz7GikE!wJl7oIe({4GPV(Z=Nhf zQW$%AOoicyN#}&#n-Ev}Kj{LS!}*mEO92m)uD`E&c)m+;4=@DkW(6XWS|9e@0oB7i4Q5 zbUZ_6_-ewOieM6W{b-6C@NMZEVefoXF=#~51QOsRPGgQ9h{ol2j+(%GFuwxyxBj)c z{Q;FD8gW{xs=_Rz5gAaL09&YLaplSEfg=1gpH&~AA2tO6u#Jolm_;Or*B*{r6Q_NO zUaua#KQWxUliR^DijVw!J(D>-R)P*sypJY&nz?J%Tx2^NWLJpb-t~Uh5^D8w1YjtT zD?HYEG1~XDDep@lDkRBtv3l<@W+h&`zS85su882YF12+br?Y2CN~$uBd$fyOt<=3L z?Y-k$Ldp|Y*g-^oJK-pg(h^oEwLIL&u_h60eyJm}uD*I*6@?KkDTVu80wAfrVt36Z z&!w5%o#-mFVtgcU;QP|Mo(hp+O-VXF&f7ZXlPxhYb!4V*O;}x8uBIGzfA>w!>e%gY zd3SWwQgGuKN?kQEih5%xmb-5#8=h348FUd*Fg;LPjt=YZM)p2ke+T)^+BUs+wZUY9 zAEl@~n!Swu5>)2c+`bIbGQ5OR)k14su6E#T^ptcBBz^hehmk1jWGa~tYd-$uAcyMg zDu&utkr>^Jd+&J+$mN4ob+3>FZE7SqlC{?F_i+wb)YTmN$trc^p_i6Cm@5fQSLzGB2E%Id6>?A+kmltDO1zvb zRO^3yJR&#;zu*>m)`RSjVu=elVU0Phalty^nfcU}h5SDt@J@|a%(4rnQ)N0uK+p{Pb|RV-cqPkLLzU3Gy10XS zEHiLAI1|hKw_RB3G=T1FDZr)WQtgGQ36<+!H^<*FMcwE(&eoAUW!zm_y5IPZp7OAa zfpBJSD7?p2UK6$p?ph;y@4`OyQ=TfPWglB(Btc72I!;{n&k=OlGco*=jE{fKAt34q zUC2LL>a*S!1)Ag~HYhMtp6Y!xt$5)-0sN5^@?A8idY;?Q>UpfzT;Cq?-sa-#(%OYL z%AQp|0|$}b)sOeHx}K8m^u{sosxOAyU%Q;}K_jcvDKp^3irjjO$WRs>no3bQg!&IZ zqHAd98yj9TzHLYDp10bI^0Y^Z-1w^Vt;DU4jJH(9dQHRL-t`hL3{Y6h=+ z(p^8Z;O;=gQHX2l8F%&a;?gh42QD|&2AQsz*WZGajyMKbZCN0RaS+qif6>My;zFpm z>LVV#=fs}PUnYFu1^J&Tip!B*TKW($6SLhaw1(#F0#Bv27_{%*gzP6R`_XCZlY>_!{Uwa|!= zdkbVLcu^aq*`qBwbB?f^78T}0334bVYlkB`^x(2_I~cDv^I<|9$M=#&~tF_{E_i` zg+r+_maEOWVEM0_8Gq8*N|4?J85JEEjn#q( z$CgG-Chv+d1{wpP?)V2`vnn9bjXA0}*rW7**7NZpR+fe5!KMf5v2Hcm55=;IuDTQ+ ziTB?9G217mmV=@K;5u~L{0C=XGXEX-0zckw VGA>!Vu=Ed((-wB-BvYTCe+T?-6cGRb literal 0 HcmV?d00001 diff --git a/muk_security/static/description/icon.png b/muk_security/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9789ede3500754ca387a05a7f0d6e5a1bca7c613 GIT binary patch literal 9262 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 \ No newline at end of file diff --git a/muk_security/static/description/index.html b/muk_security/static/description/index.html new file mode 100644 index 0000000..e4bdaf4 --- /dev/null +++ b/muk_security/static/description/index.html @@ -0,0 +1,67 @@ +
+
+

MuK Security

+

Utility Features

+

MuK IT GmbH - + www.mukit.at

+
+
+ +
+
+
+

Overview

+

Technical module to provide some utility and + security features. The module is mainly used as a dependency by + other modules and has no direct visible effect on the system.

+
+
+
+ +
+

Demo

+
+
+
User:
+
+
+
apps
+
+
+
Password:
+
+
+
demo
+
+
+ +
+ +
+

Help and Support

+
Feel free to + contact us, if you need any help with your Odoo integration or + addiontal features.
+ + +
\ No newline at end of file diff --git a/muk_security/static/description/logo.png b/muk_security/static/description/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9427ce33ea36c2ec961356d01d612bae02d48ce2 GIT binary patch literal 38064 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 literal 0 HcmV?d00001 diff --git a/muk_security/tests/__init__.py b/muk_security/tests/__init__.py new file mode 100644 index 0000000..59d0262 --- /dev/null +++ b/muk_security/tests/__init__.py @@ -0,0 +1,21 @@ +################################################################################### +# +# Copyright (C) 2018 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +from . import test_access_groups +from . import test_suspend_security \ No newline at end of file diff --git a/muk_security/tests/test_access_groups.py b/muk_security/tests/test_access_groups.py new file mode 100644 index 0000000..0f61f6f --- /dev/null +++ b/muk_security/tests/test_access_groups.py @@ -0,0 +1,83 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import os +import base64 +import logging + +from odoo import exceptions +from odoo.tests import common + +_path = os.path.dirname(os.path.dirname(__file__)) +_logger = logging.getLogger(__name__) + +class AccessGroupsTestCase(common.TransactionCase): + + def setUp(self): + super(AccessGroupsTestCase, self).setUp() + self.user_id = self.ref('base.user_demo') + self.group_id = self.ref('base.group_system') + self.groups = self.env['muk_security.access_groups'] + self.group01 = self.groups.create({ + 'name': 'Group 01', + 'explicit_users': [(6, 0, [self.user_id])]}) + self.group02 = self.groups.create({ + 'name': 'Group 02', + 'groups': [(6, 0, [self.group_id])]}) + self.user = self.env['res.users'].browse(self.user_id) + self.group = self.env['res.groups'].browse(self.group_id) + + def tearDown(self): + super(AccessGroupsTestCase, self).tearDown() + + def test_access_groups_users(self): + count = len(self.group02.users) + self.group02.write({'explicit_users': [(6, 0, [self.user_id])]}) + self.assertTrue(len(self.group02.users) > count) + + def test_access_groups_groups(self): + count = len(self.group01.users) + self.group01.write({'groups': [(6, 0, [self.group_id])]}) + self.assertTrue(len(self.group01.users) > count) + + def test_access_groups_groups_group(self): + count = len(self.group02.users) + self.group.write({'users': [(4, self.user_id)]}) + self.assertTrue(len(self.group02.users) > count) + + def test_access_groups_groups_user(self): + count = len(self.group02.users) + self.user.write({'groups_id':[(4, self.group_id)]}) + self.assertTrue(len(self.group02.users) > count) + + def test_access_groups_parent(self): + count = len(self.group02.users) + self.group02.write({'parent_group': self.group01.id}) + self.assertTrue(len(self.group02.users) > count) + + def test_access_groups_parent_multi(self): + group01 = self.groups.create({'name': 'MGroup 01'}) + group02 = self.groups.create({'name': 'MGroup 02', 'parent_group': group01.id}) + group03 = self.groups.create({'name': 'MGroup 03', 'parent_group': group02.id}) + init_count = len(group03.users) + group02.write({'explicit_users': [(6, 0, [self.user_id])]}) + self.assertTrue(len(group03.users) > init_count) + updated_count = len(group03.users) + group01.write({'groups': [(6, 0, [self.group_id])]}) + self.assertTrue(len(group03.users) > updated_count) \ No newline at end of file diff --git a/muk_security/tests/test_suspend_security.py b/muk_security/tests/test_suspend_security.py new file mode 100644 index 0000000..4823889 --- /dev/null +++ b/muk_security/tests/test_suspend_security.py @@ -0,0 +1,48 @@ +################################################################################### +# +# Copyright (C) 2017 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +import os +import base64 +import logging + +from odoo import exceptions +from odoo.tests import common + +_path = os.path.dirname(os.path.dirname(__file__)) +_logger = logging.getLogger(__name__) + +class SuspendSecurityTestCase(common.TransactionCase): + + def setUp(self): + super(SuspendSecurityTestCase, self).setUp() + + def tearDown(self): + super(SuspendSecurityTestCase, self).tearDown() + + def test_suspend_security(self): + user_id = self.env.ref('base.user_demo').id + tester = self.env.ref('base.user_root').sudo(user_id) + with self.assertRaises(exceptions.AccessError): + tester.write({'login': 'test'}) + tester.suspend_security().write({'login': 'test'}) + self.assertEqual(tester.login, 'test') + self.assertEqual(tester.write_uid.id, user_id) + + def test_normalize(self): + self.env['res.users'].browse(self.env['res.users'].suspend_security().env.uid) \ No newline at end of file diff --git a/muk_security/tools/__init__.py b/muk_security/tools/__init__.py new file mode 100644 index 0000000..bd6f764 --- /dev/null +++ b/muk_security/tools/__init__.py @@ -0,0 +1,20 @@ +################################################################################### +# +# Copyright (C) 2018 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +from . import security \ No newline at end of file diff --git a/muk_security/tools/security.py b/muk_security/tools/security.py new file mode 100644 index 0000000..ad90aa0 --- /dev/null +++ b/muk_security/tools/security.py @@ -0,0 +1,34 @@ +################################################################################### +# +# Copyright (C) 2018 MuK IT GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### + +class NoSecurityUid(int): + + def __int__(self): + return self + + def __eq__(self, other): + if isinstance(other, int): + return False + return super(NoSecurityUid, self).__int__() == other + + def __iter__(self): + yield super(NoSecurityUid, self).__int__() + + def __hash__(self): + return super(NoSecurityUid, self).__hash__() \ No newline at end of file diff --git a/muk_security/views/access_groups.xml b/muk_security/views/access_groups.xml new file mode 100644 index 0000000..e7a337d --- /dev/null +++ b/muk_security/views/access_groups.xml @@ -0,0 +1,73 @@ + + + + + + + + muk_security_access_groups.tree + muk_security.access_groups + primary + + + + + + + + + + + + + muk_security_access_groups.form + muk_security.access_groups + primary + + + + + + + + + + + + + + + + + + + + + + + + + Access Groups + muk_security.access_groups + tree,form + + + + + \ No newline at end of file