Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use lemonldap api to edit the configuration #8

Open
l00ptr opened this issue Jan 31, 2022 · 4 comments
Open

use lemonldap api to edit the configuration #8

l00ptr opened this issue Jan 31, 2022 · 4 comments
Labels
enhancement New feature or request

Comments

@l00ptr
Copy link
Contributor

l00ptr commented Jan 31, 2022

Hello,

It could be nice to be able to adapt the config of a running LemonLDAP through the API within ansible. I know there is an API for the manager, but don't know if we have enough features there to create an Ansible module for this purpose. What do you think ? It could be fun to explore and create a first PoC with some very simple object (menucat or menuapp). If you think that's good idea, i will spend some time the few next weeks exploring this.

Best regards,
l.

@coudot
Copy link
Member

coudot commented Feb 2, 2022

Hello, it is a good idea. We already tried this for W'Sweet, but code is not yet published.

@l00ptr
Copy link
Contributor Author

l00ptr commented Feb 14, 2022

ok, could you share this code ? I would like those features (it could be easier than writing them from scratch)

@coudot
Copy link
Member

coudot commented Feb 17, 2022

Here is an ansible module written by @maxbes to create OIDC RP through API:

#!/usr/bin/python

# Copyright: (c) 2018, Terry Jones <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

ANSIBLE_METADATA = {
    'metadata_version': '1.1',
    'status': ['preview'],
    'supported_by': 'community'
}

DOCUMENTATION = '''
---
module: lemonldap_oidc_rp

short_description: This module creates a new OIDC Relying Party in LemonLDAP

version_added: "2.8"

description:
    - "This module uses the Manager API to create a new OIDC Relying Party"

options:
    name:
        description:
            - The arbitrary, internal name of this party in LemonLDAP::NG
        required: true
    state:
        description:
            - Whether the party should exist or not, taking action if the state is different from what is stated
        type: str
        choices: [ absent, present ]
        default: present
    options:
        description:
            - Dictionnary of OIDC Relying Party options
        type: dict
        clientId=dict(type='str', required=False),
    clientId:
        description:
            - The OpenID Connect Client ID of this party
    redirectUris:
        description:
            - The list of allowed redirect URIs for this party
        type: list
    exported_variables:
        description:
            - Dictionnary of LemonLDAP::NG session attributes to send to this party
            - The key of each dictionnary is the name of the OIDC claim
            - The value is the name of LemonLDAP session attribute to use for this claim
        type: dict
    extra_claims:
        description:
            - Dictionary of additional scopes you want this party to support
            - The key of each dictionnary is the name of a scope
            - The value is the space-separated list of OIDC claims released by this scope
        type: dict
    api_url:
        description:
            - URL of the LemonLDAP::NG Manager API
        type: str
        required: true
    url_username:
        description:
            - Username to login on the Manager API as
        type: str
    url_password:
        description:
            - Password for the Manager API
        type: str
    timeout:
        description:
            - Request timeout for the Manager API
        type: int
        default: 60

author:
    - Your Name (@yourhandle)
'''

EXAMPLE = '''
# Create a new OIDC Relying party
- name: create
lemonldap_oidc_rp:
    api_url: https://manager-api.dev.wsweet.cloud/
    url_username: manager
    url_password: password
    name: myopenidapp
    state: present
    clientId: myclientid
    options:
        clientSecret: myclientsecret
    redirectUris:
      - https://myapp.dev.wsweet.cloud/oauth2/callback
    exported_variables:
        email: mail
        family_name: sn
        given_name: givenName
        preferred_username: uid
'''

RETURN = '''
original_message:
    description: The original name param that was passed in
    type: str
    returned: always
message:
    description: The output message that the test module generates
    type: str
    returned: always
'''

import json

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.lemonldap_api import LemonApi,lemon_base_spec

class OidcRpApi(LemonApi):

    def get_object(self):
        name = self.module.params['name']
        url = "/providers/oidc/rp/{0}".format(name)

        return self.api_request(url)


    def differs(self, current_object):
        if self.module.params['clientId'] != current_object['clientId']:
            return True
        if self._differs_hash(current_object, 'options', 'options'):
            return True
        if self._differs_hash(current_object, 'exported_variables', 'exportedVars'):
            return True
        if self._differs_hash(current_object, 'extra_claims', 'extraClaims'):
            return True
        return False

    def _differs_hash(self, current_object, option_name, payload_key):
        specification = self.module.params[option_name]
        state = current_object[payload_key]
        if specification is None:
            return False
        for keyname in specification.keys():
            if not keyname in state or specification[keyname] != state[keyname]:
                return True


    def build_payload(self,confKey = None):
        data = {}
        if not confKey is None:
            data['confKey'] = confKey
        if 'clientId' in self.module.params:
            data['clientId'] = self.module.params['clientId']
        if 'redirectUris' in self.module.params:
            data['redirectUris'] = self.module.params['redirectUris']
        if 'options' in self.module.params:
            data['options'] = self.module.params['options']
        if 'exported_variables' in self.module.params:
            data['exportedVars'] = self.module.params['exported_variables']
        if 'extra_claims' in self.module.params:
            data['extraClaims'] = self.module.params['extra_claims']
        return data

    def create(self):
        url = "/providers/oidc/rp"
        data = self.build_payload(self.module.params['name'])
        self.api_request(url, method='POST', data=data, expect=201)
        self.module.exit_json(changed=True)

    def update(self):
        url = "/providers/oidc/rp/{0}".format(self.module.params['name'])
        data = self.build_payload()
        self.api_request(url, method='PATCH', data=data, expect=204)
        self.module.exit_json(changed=True)

    def delete(self):
        url = "/providers/oidc/rp/{0}".format(self.module.params['name'])
        self.api_request(url, method='DELETE', expect=204)
        self.module.exit_json(changed=True)


def run_module():
    # define available arguments/parameters a user can pass to the module
    module_args = lemon_base_spec(dict(
        name=dict(type='str', required=True),
        clientId=dict(type='str', required=False),
        redirectUris=dict(type='list', elements='str', required=False,),
        options=dict(type='dict', required=False,),
        exported_variables=dict(type='dict', required=False,),
        extra_claims=dict(type='dict', required=False,),
        state=dict(type='str', default="present", choices=["present", "absent"]),
    ))

    # seed the result dict in the object
    # we primarily care about changed and state
    # change is if this module effectively modified the target
    # state will include any data that you want your module to pass back
    # for consumption, for example, in a subsequent task
    result = dict(
        changed=False,
    )

    # the AnsibleModule object will be our abstraction working with Ansible
    # this includes instantiation, a couple of common attr would be the
    # args/params passed to the execution, as well as if the module
    # supports check mode
    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=True
    )

    api = OidcRpApi(module)

    api.run()

def main():
    run_module()

if __name__ == '__main__':
    main()

@coudot coudot added the enhancement New feature or request label Feb 17, 2022
@l00ptr
Copy link
Contributor Author

l00ptr commented Feb 18, 2022

great thx for sharing this piece of code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants