app_password.ymlΒΆ

---
# Param: stalwart_bots - up to STALWART_JMAP_MAX_METHOD_CALLS bot entries of stalwart_accounts; each method call addresses one bot through its accountId.
- name: "πŸ“– List the app passwords of the bots"
  include_tasks: "{{ role_path }}/tasks/jmap_call.yml"
  vars:
    stalwart_jmap_body: >-
      {%- set calls = [] -%}
      {%- for bot in stalwart_bots -%}
      {%-   set _ = calls.append(['x:AppPassword/get', {'accountId': bot.id, 'ids': none}, bot.address]) -%}
      {%- endfor -%}
      {{ {'using': ['urn:ietf:params:jmap:core', 'urn:stalwart:jmap'], 'methodCalls': calls} | to_json }}

- name: "πŸ” Decide which bots need a new app password"
  ansible.builtin.set_fact:
    stalwart_app_password_plan: >-
      {%- set listed = {} -%}
      {%- for method, result, call in stalwart_jmap.methodResponses -%}
      {%-   set _ = listed.update({call: result.list}) -%}
      {%- endfor -%}
      {%- set plan = [] -%}
      {%- for bot in stalwart_bots -%}
      {%-   set owned = listed[bot.address] | selectattr('description', 'equalto', STALWART_BOT_APP_PASSWORD_DESCRIPTION)
                        | map(attribute='id') | list -%}
      {%-   set retired = listed[bot.address] | selectattr('description', 'equalto', STALWART_BOT_APP_PASSWORD_RETIRED_DESCRIPTION)
                          | map(attribute='id') | list -%}
      {%-   set renew = (owned | length != 1) or (bot.stored | length == 0) or (bot.stored == bot.secret) -%}
      {%-   set _ = plan.append({'key': bot.key, 'address': bot.address, 'id': bot.id, 'owned': owned,
                                 'renew': renew, 'drop': retired + (owned if renew else [])}) -%}
      {%- endfor -%}
      {{ plan }}
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: "πŸ”‘ Replace the app passwords of the bots that need a new one"
  when: stalwart_app_password_plan | selectattr('renew') | list | length > 0
  block:
    - name: "πŸ”‘ Mint the new app passwords"
      include_tasks: "{{ role_path }}/tasks/jmap_call.yml"
      vars:
        stalwart_jmap_body: >-
          {%- set calls = [] -%}
          {%- for bot in stalwart_app_password_plan if bot.renew -%}
          {%-   set _ = calls.append(['x:AppPassword/set', {'accountId': bot.id,
                  'create': {'p': {'description': STALWART_BOT_APP_PASSWORD_DESCRIPTION}}}, bot.address]) -%}
          {%- endfor -%}
          {{ {'using': ['urn:ietf:params:jmap:core', 'urn:stalwart:jmap'], 'methodCalls': calls} | to_json }}

    - name: "πŸ” Read the minted app passwords"
      ansible.builtin.set_fact:
        stalwart_app_password_minted: >-
          {%- set results = {} -%}
          {%- for method, result, call in stalwart_jmap.methodResponses -%}
          {%-   set _ = results.update({call: result}) -%}
          {%- endfor -%}
          {%- set minted = [] -%}
          {%- for bot in stalwart_app_password_plan if bot.renew -%}
          {%-   set _ = minted.append({'key': bot.key, 'address': bot.address,
                                       'secret': results[bot.address].created.p.secret}) -%}
          {%- endfor -%}
          {{ minted }}
      no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

    - name: "πŸ’Ύ Persist the new app passwords"
      ansible.builtin.include_role:
        name: sys-token-store
        tasks_from: write_many.yml
      vars:
        sys_token_store_entries: >-
          {%- set entries = [] -%}
          {%- for minted in stalwart_app_password_minted -%}
          {%-   set _ = entries.append({'user_key': minted.key, 'app': application_id, 'token': minted.secret}) -%}
          {%- endfor -%}
          {{ entries }}
      no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: "🏷️ Retire the app passwords the new ones replace"
  include_tasks: "{{ role_path }}/tasks/jmap_call.yml"
  vars:
    stalwart_jmap_body: >-
      {%- set calls = [] -%}
      {%- for bot in stalwart_app_password_plan if bot.renew and bot.owned | length > 0 -%}
      {%-   set _ = calls.append(['x:AppPassword/set', {'accountId': bot.id,
              'update': dict(bot.owned | zip([{'description': STALWART_BOT_APP_PASSWORD_RETIRED_DESCRIPTION}] * (bot.owned | length)))},
              bot.address]) -%}
      {%- endfor -%}
      {{ {'using': ['urn:ietf:params:jmap:core', 'urn:stalwart:jmap'], 'methodCalls': calls} | to_json }}
  when: stalwart_app_password_plan | selectattr('renew') | selectattr('owned') | list | length > 0

- name: "πŸ“₯ Defer dropping the retired app passwords"
  when: stalwart_app_password_plan | selectattr('drop') | list | length > 0
  block:
    - name: "πŸ“₯ Record the retired app passwords per bot"
      ansible.builtin.set_fact:
        stalwart_app_password_stale: >-
          {%- set stale = {} -%}
          {%- for bot in stalwart_app_password_plan if bot.drop | length > 0 -%}
          {%-   set _ = stale.update({bot.address: {'account': bot.id, 'ids': bot.drop}}) -%}
          {%- endfor -%}
          {{ stalwart_app_password_stale | combine(stale) }}

    - name: "πŸ“₯ Queue the drop for the service-loader post-load flush"
      include_role:
        name: sys-service-loader
        tasks_from: list_or_shoot.yml
      vars:
        list_or_shoot_roles:
          - web-app-stalwart