├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── dependabot.yml ├── label-commenter-config.yml ├── pull_request_template.md └── workflows │ ├── auto-tag-new-version.yml │ ├── close_stale_issue.yml │ ├── continuous-integration.yml │ ├── label-commenter.yml │ ├── locales-sync.yml │ ├── locales-update-source.yml │ └── release.yml ├── .gitignore ├── .php-cs-fixer.php ├── .phpcs.xml ├── .tx └── config ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── ajax └── dropdownAuthorization.php ├── composer.json ├── composer.lock ├── docs ├── glpi_network.png ├── logo.png └── screenshots │ ├── config.png │ └── config_oauth_mailcollector.png ├── front ├── application.form.php ├── application.php ├── authorization.callback.php ├── authorization.form.php └── config.form.php ├── hook.php ├── inc ├── application.class.php ├── authorization.class.php ├── hook.class.php ├── imap │ ├── imapoauthprotocol.class.php │ └── imapoauthstorage.class.php ├── mailcollectorfeature.class.php ├── oauth │ └── ownerdetails.class.php └── provider │ ├── azure.class.php │ ├── google.class.php │ └── providerinterface.class.php ├── locales ├── en_GB.mo ├── en_GB.po ├── es_EC.mo ├── es_EC.po ├── es_MX.mo ├── es_MX.po ├── fr_FR.mo ├── fr_FR.po ├── oauthimap.pot ├── pt_BR.mo ├── pt_BR.po ├── sk_SK.mo └── sk_SK.po ├── oauthimap.xml ├── phpstan.neon ├── setup.php └── tools └── HEADER /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve oauthimap 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | 8 | Dear GLPI plugin user. 9 | 10 | **⚠️ Please never use standard issues to report security problems. See [security policy](https://github.com/pluginsGLPI/oauthimap/security/policy) for more details. ⚠️** 11 | 12 | BEFORE SUBMITTING YOUR ISSUE, please make sure to read and follow these steps: 13 | 14 | * We do not track feature requests nor enhancements here. Propose them on the [suggest dedicated site](https://suggest.glpi-project.org). 15 | * Keep this tracker in ENGLISH. If you want support in your language, the [community forum](https://forum.glpi-project.org) is the best place. 16 | * Always try to reproduce your issue at least on latest stable release. 17 | 18 | The GLPI team. 19 | - type: markdown 20 | attributes: 21 | value: | 22 | ## Professional Support 23 | 24 | We do not guarantee any processing / resolution time for community issues. 25 | 26 | If you need a quick fix or any guarantee, you should consider to buy a GLPI Network Subscription. 27 | 28 | More information here: https://glpi-project.org/subscriptions/ 29 | - type: checkboxes 30 | id: terms 31 | attributes: 32 | label: Code of Conduct 33 | description: By submitting this issue, you agree to follow hereinabove rules and [Contribution guide](https://github.com/glpi-project/glpi/blob/main/CONTRIBUTING.md) 34 | options: 35 | - label: I agree to follow this project's Code of Conduct 36 | validations: 37 | required: true 38 | - type: checkboxes 39 | attributes: 40 | label: Is there an existing issue for this? 41 | description: Please search to see if an issue already exists for the bug you encountered. 42 | options: 43 | - label: I have searched the existing issues 44 | validations: 45 | required: true 46 | - type: input 47 | id: glpi-version 48 | attributes: 49 | label: GLPI Version 50 | description: What version of our GLPI are you running? 51 | validations: 52 | required: true 53 | - type: input 54 | id: plugin-version 55 | attributes: 56 | label: Plugin version 57 | description: What version of `oauthimap` are you running? 58 | validations: 59 | required: true 60 | - type: textarea 61 | attributes: 62 | label: Bug description 63 | description: A concise description of the problem you are experiencing and what you expected to happen. 64 | validations: 65 | required: false 66 | - type: textarea 67 | id: logs 68 | attributes: 69 | label: Relevant log output 70 | description: | 71 | Please copy and paste any relevant log output. Find them in `*-error.log` files under `glpi/files/_log/`. 72 | 73 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 74 | render: shell 75 | - type: input 76 | id: url 77 | attributes: 78 | label: Page URL 79 | description: If applicable, page URL where the bug happens. 80 | validations: 81 | required: false 82 | - type: textarea 83 | attributes: 84 | label: Steps To reproduce 85 | description: Steps to reproduce the behavior. 86 | placeholder: | 87 | 1. With this config... 88 | 2. Go to... 89 | 3. Scroll down to... 90 | 4. See error... 91 | validations: 92 | required: false 93 | - type: textarea 94 | attributes: 95 | label: Your GLPI setup information 96 | description: Please copy and paste information you will find in GLPI in `Setup > General` menu, `System` tab. 97 | validations: 98 | required: false 99 | - type: textarea 100 | attributes: 101 | label: Anything else? 102 | description: Add any other context about the problem here. 103 | validations: 104 | required: false 105 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: "GLPI professional support" 4 | url: "https://services.glpi-network.com" 5 | about: "Get professional support from the editor and a network of local partners." 6 | - name: "Find an official partner" 7 | url: "https://glpi-project.org/partners/" 8 | about: "Get support to deploy GLPI in a professional manner." 9 | - name: "GLPI Community Forum" 10 | url: "https://forum.glpi-project.org" 11 | about: "Ask questions and get help from the community." 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Ensure GitHub Actions are used in their latest version 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | 9 | # Strategy for composer dependencies 10 | - package-ecosystem: "composer" 11 | directory: "/" 12 | schedule: 13 | interval: "monthly" 14 | allow: 15 | - dependency-type: "direct" 16 | open-pull-requests-limit: 100 17 | versioning-strategy: "increase" 18 | groups: 19 | dev-dependencies: 20 | dependency-type: "development" 21 | -------------------------------------------------------------------------------- /.github/label-commenter-config.yml: -------------------------------------------------------------------------------- 1 | labels: 2 | - name: "invalid" 3 | labeled: 4 | issue: 5 | body: | 6 | This issue has been closed because you did not provide the requested information. 7 | action: "close" 8 | - name: "support" 9 | labeled: 10 | issue: 11 | body: | 12 | This issue has been closed as we only track bugs here. 13 | 14 | You can get community support on [forums](https://forum.glpi-project.org/) or you can consider [taking a subscription](https://glpi-project.org/subscriptions/) to get professional support. 15 | You can also [contact GLPI editor team](https://portal.glpi-network.com/contact-us) directly. 16 | action: close 17 | - name: "feature suggestion" 18 | labeled: 19 | issue: 20 | body: | 21 | This issue has been closed as we only track bugs here. 22 | 23 | You can open a topic to discuss with community about this enhancement on [suggestion website](https://glpi.userecho.com/). 24 | You can also [contact GLPI editor team](https://portal.glpi-network.com/contact-us) directly if you are willing to sponsor this feature. 25 | action: close 26 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Checklist before requesting a review 2 | 3 | *Please delete options that are not relevant.* 4 | 5 | - [ ] I have performed a self-review of my code. 6 | - [ ] I have added tests (when available) that prove my fix is effective or that my feature works. 7 | - [ ] I have updated the CHANGELOG with a short functional description of the fix or new feature. 8 | - [ ] This change requires a documentation update. 9 | 10 | ## Description 11 | 12 | - It fixes # (issue number, if applicable) 13 | - Here is a brief description of what this PR does 14 | 15 | ## Screenshots (if appropriate): 16 | -------------------------------------------------------------------------------- /.github/workflows/auto-tag-new-version.yml: -------------------------------------------------------------------------------- 1 | name: "Automatically tag new version" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths: 8 | - "setup.php" 9 | 10 | jobs: 11 | auto-tag-new-version: 12 | name: "Automatically tag new version" 13 | uses: "glpi-project/plugin-release-workflows/.github/workflows/auto-tag-new-version.yml@v1" 14 | secrets: 15 | github-token: "${{ secrets.AUTOTAG_TOKEN }}" 16 | -------------------------------------------------------------------------------- /.github/workflows/close_stale_issue.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues' 2 | on: 3 | schedule: 4 | - cron: '0 8 * * *' 5 | 6 | jobs: 7 | stale: 8 | if: github.repository == 'pluginsGLPI/oauthimap' 9 | permissions: 10 | issues: write # for actions/stale to close stale issues 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/stale@v9 14 | with: 15 | stale-issue-message: >- 16 | There has been no activity on this issue for some time and therefore it is considered stale 17 | and will be closed automatically in 10 days. 18 | 19 | 20 | If this issue is related to a bug, please try to reproduce on latest release. If the problem persist, 21 | feel free to add a comment to revive this issue. 22 | 23 | If it is related to a new feature, please open a topic to discuss with community about this enhancement 24 | on [suggestion website](https://glpi.userecho.com/). 25 | 26 | 27 | You may also consider taking a [subscription](https://glpi-project.org/subscriptions/) to get professionnal 28 | support or [contact GLPI editor team](https://portal.glpi-network.com/contact-us) directly. 29 | days-before-issue-stale: 15 30 | days-before-pr-stale: -1 # PR will be marked as stale manually. 31 | days-before-close: 5 32 | exempt-issue-labels: "bug,enhancement,question,security" # Issues with "bug", "enhancement", "question" or "security" labels will not be marked as stale 33 | exempt-all-milestones: true # Do not check issues/PR with defined milestone. 34 | ascending: true # First check older issues/PR. 35 | operations-per-run: 750 # Max API calls per run. 36 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: "Continuous integration" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | tags: 8 | - "*" 9 | pull_request: 10 | schedule: 11 | - cron: "0 0 * * *" 12 | workflow_dispatch: 13 | 14 | concurrency: 15 | group: "${{ github.workflow }}-${{ github.ref }}" 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | generate-ci-matrix: 20 | name: "Generate CI matrix" 21 | uses: "glpi-project/plugin-ci-workflows/.github/workflows/generate-ci-matrix.yml@v1" 22 | with: 23 | glpi-version: "10.0.x" 24 | ci: 25 | name: "GLPI ${{ matrix.glpi-version }} - php:${{ matrix.php-version }} - ${{ matrix.db-image }}" 26 | needs: "generate-ci-matrix" 27 | strategy: 28 | fail-fast: false 29 | matrix: ${{ fromJson(needs.generate-ci-matrix.outputs.matrix) }} 30 | uses: "glpi-project/plugin-ci-workflows/.github/workflows/continuous-integration.yml@v1" 31 | with: 32 | plugin-key: "oauthimap" 33 | glpi-version: "${{ matrix.glpi-version }}" 34 | php-version: "${{ matrix.php-version }}" 35 | db-image: "${{ matrix.db-image }}" 36 | -------------------------------------------------------------------------------- /.github/workflows/label-commenter.yml: -------------------------------------------------------------------------------- 1 | name: "Label commenter" 2 | 3 | on: 4 | issues: 5 | types: 6 | - "labeled" 7 | - "unlabeled" 8 | 9 | jobs: 10 | comment: 11 | permissions: 12 | contents: "read" 13 | issues: "write" 14 | runs-on: "ubuntu-latest" 15 | steps: 16 | - name: "Checkout" 17 | uses: "actions/checkout@v4" 18 | 19 | - name: "Label commenter" 20 | uses: "peaceiris/actions-label-commenter@v1" 21 | -------------------------------------------------------------------------------- /.github/workflows/locales-sync.yml: -------------------------------------------------------------------------------- 1 | name: "Synchronize locales" 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * 1-5" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | sync-with-transifex: 10 | name: "Sync with transifex" 11 | uses: "glpi-project/plugin-translation-workflows/.github/workflows/transifex-sync.yml@v1" 12 | secrets: 13 | github-token: "${{ secrets.LOCALES_SYNC_TOKEN }}" 14 | transifex-token: "${{ secrets.TRANSIFEX_TOKEN }}" 15 | -------------------------------------------------------------------------------- /.github/workflows/locales-update-source.yml: -------------------------------------------------------------------------------- 1 | name: "Update locales sources" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | jobs: 9 | push-on-transifex: 10 | name: "Push locales sources" 11 | uses: "glpi-project/plugin-translation-workflows/.github/workflows/transifex-push-sources.yml@v1" 12 | secrets: 13 | transifex-token: "${{ secrets.TRANSIFEX_TOKEN }}" 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Publish release" 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish-release: 10 | permissions: 11 | contents: "write" 12 | name: "Publish release" 13 | uses: "glpi-project/plugin-release-workflows/.github/workflows/publish-release.yml@v1" 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | vendor/ 3 | .gh_token 4 | *.min.* 5 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | in(__DIR__) 8 | ->name('*.php'); 9 | 10 | $config = new Config(); 11 | 12 | $rules = [ 13 | '@PER-CS2.0' => true, 14 | 'trailing_comma_in_multiline' => ['elements' => ['arguments', 'array_destructuring', 'arrays']], // For PHP 7.4 compatibility 15 | ]; 16 | 17 | return $config 18 | ->setRules($rules) 19 | ->setFinder($finder) 20 | ->setUsingCache(false); 21 | -------------------------------------------------------------------------------- /.phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | . 4 | /.git/ 5 | ^vendor/ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [o:teclib:p:glpi-project-plugin-oauthimap:r:oauthimap-pot] 5 | file_filter = locales/.po 6 | source_file = locales/oauthimap.pot 7 | source_lang = en 8 | type = PO 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [UNRELEASE] 9 | 10 | ## Fix 11 | 12 | - Fix UI for the copy to clipboard button 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oauth IMAP client for GLPI 2 | 3 | This plugins provides IMAP SASL XOAUTH2 authentication mechanism for mail receivers. 4 | 5 | Currently implemented for: 6 | 7 | * [Google (G Suite and Gmail)](https://developers.google.com/gmail/imap/xoauth2-protocol) 8 | * [Microsoft (Office 365 via Azure AD)](https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth) 9 | 10 | ![Configuration page](docs/screenshots/config.png) 11 | ![Mail receiver setup](docs/screenshots/config_oauth_mailcollector.png) 12 | 13 | ## Documentation 14 | 15 | We maintain a detailed documentation here -> [Documentation](https://glpi-plugins.readthedocs.io/en/latest/oauthimap/index.html) 16 | 17 | ## Professional Services 18 | 19 | ![GLPI Network](docs/glpi_network.png "GLPI network") 20 | 21 | The GLPI Network services are available through our [Partner's Network](http://www.teclib-edition.com/en/partners/). We provide special training, bug fixes with editor subscription, contributions for new features, and more. 22 | 23 | Obtain a personalized service experience, associated with benefits and opportunities. 24 | 25 | ## Copying 26 | 27 | * **Code**: you can redistribute it and/or modify 28 | it under the terms of the GNU General Public License ([GPL-2.0](https://www.gnu.org/licenses/gpl-2.0.en.html)). 29 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | **⚠️ Please never use standard issues to report security problems; vulnerabilities are published once a fix release is available. ⚠️** 4 | 5 | ## Reporting a Vulnerability 6 | 7 | If you found a security issue, please contact us by: 8 | 9 | - [our huntr page](https://huntr.dev/repos/pluginsGLPI/oauthimap/) 10 | - a mail to \[glpi-security AT ow2.org\] 11 | 12 | You should provide us all details about the issue and the way to reproduce it. 13 | You may also provide a script that can be used to check the issue exists. 14 | 15 | Once the report will be handled, and if the issue is not yet fixed (or in progress) 16 | we'll add it to the GitHub security tab, and add you as observer. Meanwhile, 17 | you will reserve a CVE for the issue. 18 | 19 | Thank you for improving the security of GLPI and its plugins. 20 | 21 | ## Supported Versions 22 | 23 | We follow the same version support policy as GLPI. 24 | This means that we provide security patches to versions of the plugin that target a version of GLPI itself maintained from a security point of view. 25 | -------------------------------------------------------------------------------- /ajax/dropdownAuthorization.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | include('../../../inc/includes.php'); 32 | header('Content-Type: text/html; charset=UTF-8'); 33 | Html::header_nocache(); 34 | 35 | Session::checkLoginUser(); 36 | 37 | /** @var \DBmysql $DB */ 38 | global $DB; 39 | 40 | $iterator = $DB->request( 41 | [ 42 | 'FROM' => PluginOauthimapAuthorization::getTable(), 43 | 'WHERE' => [ 44 | PluginOauthimapApplication::getForeignKeyField() => $_POST['application_id'] ?? null, 45 | ], 46 | ], 47 | ); 48 | $authorizations = [ 49 | '-1' => __('Create authorization for another user', 'oauthimap'), 50 | ]; 51 | $value = -1; 52 | foreach ($iterator as $row) { 53 | $authorizations[$row['id']] = $row['email']; 54 | if (array_key_exists('selected', $_POST) && $row['email'] == $_POST['selected']) { 55 | $value = $row['id']; 56 | } 57 | } 58 | 59 | Dropdown::showFromArray( 60 | PluginOauthimapAuthorization::getForeignKeyField(), 61 | $authorizations, 62 | [ 63 | 'display_emptychoice' => false, 64 | 'value' => $value, 65 | ], 66 | ); 67 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php": ">=7.4" 4 | }, 5 | "require-dev": { 6 | "friendsofphp/php-cs-fixer": "^3.75", 7 | "glpi-project/tools": "^0.7.5", 8 | "php-parallel-lint/php-parallel-lint": "^1.4", 9 | "phpstan/extension-installer": "^1.4", 10 | "phpstan/phpstan": "^2.1", 11 | "phpstan/phpstan-deprecation-rules": "^2.0" 12 | }, 13 | "provide": { 14 | "guzzlehttp/guzzle": "*" 15 | }, 16 | "config": { 17 | "optimize-autoloader": true, 18 | "platform": { 19 | "php": "7.4.0" 20 | }, 21 | "sort-packages": true, 22 | "allow-plugins": { 23 | "phpstan/extension-installer": true 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/glpi_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/docs/glpi_network.png -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/docs/logo.png -------------------------------------------------------------------------------- /docs/screenshots/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/docs/screenshots/config.png -------------------------------------------------------------------------------- /docs/screenshots/config_oauth_mailcollector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/docs/screenshots/config_oauth_mailcollector.png -------------------------------------------------------------------------------- /front/application.form.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | include('../../../inc/includes.php'); 32 | 33 | Session::checkLoginUser(); 34 | 35 | /** @var array $_UPOST */ 36 | global $_UPOST; 37 | 38 | $dropdown = new PluginOauthimapApplication(); 39 | 40 | if (isset($_POST['id']) && isset($_POST['request_authorization'])) { 41 | $dropdown->check($_POST['id'], UPDATE); 42 | $dropdown->redirectToAuthorizationUrl(); 43 | } else { 44 | Html::requireJs('clipboard'); 45 | 46 | if (array_key_exists('client_secret', $_POST) && array_key_exists('client_secret', $_UPOST)) { 47 | // Client secret must not be altered. 48 | $_POST['client_secret'] = $_UPOST['client_secret']; 49 | } 50 | 51 | include(GLPI_ROOT . '/front/dropdown.common.form.php'); 52 | } 53 | -------------------------------------------------------------------------------- /front/application.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | include('../../../inc/includes.php'); 32 | 33 | Session::checkLoginUser(); 34 | 35 | $dropdown = new PluginOauthimapApplication(); 36 | include(GLPI_ROOT . '/front/dropdown.common.php'); 37 | -------------------------------------------------------------------------------- /front/authorization.callback.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | if (!array_key_exists('cookie_refresh', $_GET)) { 32 | // Session cookie will not be accessible when user will be redirected from provider website 33 | // if `session.cookie_samesite` configuration value is `strict`. 34 | // Redirecting on self using `http-equiv="refresh"` will get around this limitation. 35 | $url = htmlspecialchars( 36 | $_SERVER['REQUEST_URI'] . (strpos($_SERVER['REQUEST_URI'], '?') !== false ? '&' : '?') . 'cookie_refresh', 37 | ); 38 | 39 | echo << 41 | 42 | 43 | 44 | 45 | 46 | HTML; 47 | exit; 48 | } 49 | 50 | include('../../../inc/includes.php'); 51 | 52 | $application = new PluginOauthimapApplication(); 53 | $authorization = new PluginOauthimapAuthorization(); 54 | 55 | $application_id = $_SESSION[PluginOauthimapApplication::getForeignKeyField()] ?? null; 56 | 57 | $success = false; 58 | if ( 59 | array_key_exists('error', $_GET) && !empty($_GET['error']) 60 | || array_key_exists('error_description', $_GET) && !empty($_GET['error_description']) 61 | ) { 62 | // Got an error, probably user denied access 63 | Session::addMessageAfterRedirect( 64 | sprintf(__('Authorization failed with error: %s', 'oauthimap'), $_GET['error_description'] ?? $_GET['error']), 65 | false, 66 | ERROR, 67 | ); 68 | } elseif ( 69 | $application_id === null 70 | || !array_key_exists('state', $_GET) 71 | || !array_key_exists('oauth2state', $_SESSION) 72 | || $_GET['state'] !== $_SESSION['oauth2state'] 73 | ) { 74 | Session::addMessageAfterRedirect(__('Unable to verify authorization code', 'oauthimap'), false, ERROR); 75 | } elseif (!array_key_exists('code', $_GET)) { 76 | Session::addMessageAfterRedirect(__('Unable to get authorization code', 'oauthimap'), false, ERROR); 77 | } elseif (!$authorization->createFromCode($application_id, $_GET['code'])) { 78 | Session::addMessageAfterRedirect(__('Unable to save authorization code', 'oauthimap'), false, ERROR); 79 | } else { 80 | $success = true; 81 | } 82 | 83 | $callback_callable = $_SESSION['plugin_oauthimap_callback_callable'] ?? null; 84 | 85 | if (is_callable($callback_callable)) { 86 | $callback_params = $_SESSION['plugin_oauthimap_callback_params'] ?? []; 87 | call_user_func_array($callback_callable, [$success, $authorization, $callback_params]); 88 | } 89 | 90 | // Redirect to application form/list if callback action does not exit yet 91 | if ($application->getFromDB($application_id)) { 92 | $url = $application->getLinkURL(); 93 | } else { 94 | $url = $application->getSearchURL(true); 95 | } 96 | 97 | Html::redirect($url); 98 | -------------------------------------------------------------------------------- /front/authorization.form.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | include('../../../inc/includes.php'); 32 | 33 | $authorization = new PluginOauthimapAuthorization(); 34 | $application = new PluginOauthimapApplication(); 35 | 36 | if (isset($_POST['id']) && isset($_POST['delete'])) { 37 | $authorization->check($_POST['id'], DELETE); 38 | $authorization->delete($_POST); 39 | 40 | Html::back(); 41 | } elseif (isset($_POST['id']) && isset($_POST['update'])) { 42 | $authorization->check($_POST['id'], UPDATE); 43 | if ( 44 | $authorization->update($_POST) 45 | && $application->getFromDB($authorization->fields[$application->getForeignKeyField()]) 46 | ) { 47 | Html::redirect($application->getLinkURL()); 48 | } 49 | 50 | Html::back(); 51 | } elseif (isset($_REQUEST['id']) && isset($_REQUEST['diagnose'])) { 52 | $authorization->check($_REQUEST['id'], READ); 53 | 54 | $authorization = new PluginOauthimapAuthorization(); 55 | $application = new PluginOauthimapApplication(); 56 | 57 | Html::popHeader($application::getTypeName(Session::getPluralNumber())); 58 | $authorization->check($_REQUEST['id'], READ); 59 | 60 | $authorization->showDiagnosticForm($_POST); 61 | 62 | Html::popFooter(); 63 | } elseif (isset($_GET['id'])) { 64 | $application = new PluginOauthimapApplication(); 65 | $application->displayCentralHeader(); 66 | $authorization->display( 67 | [ 68 | 'id' => $_GET['id'], 69 | ], 70 | ); 71 | Html::footer(); 72 | } else { 73 | Html::displayErrorAndDie('lost'); 74 | } 75 | -------------------------------------------------------------------------------- /front/config.form.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | include("../../../inc/includes.php"); 32 | Session::checkLoginUser(); 33 | 34 | $dropdown = new PluginOauthimapApplication(); 35 | include(GLPI_ROOT . "/front/dropdown.common.form.php"); 36 | -------------------------------------------------------------------------------- /hook.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | function plugin_oauthimap_install() 32 | { 33 | $version = plugin_version_oauthimap(); 34 | $migration = new Migration($version['version']); 35 | 36 | // Parse inc directory 37 | foreach (glob(dirname(__FILE__) . '/inc/*') as $filepath) { 38 | // Load *.class.php files and get the class name 39 | if (preg_match("/inc.(.+)\.class.php$/", $filepath, $matches)) { 40 | $classname = 'PluginOauthimap' . ucfirst($matches[1]); 41 | include_once($filepath); 42 | // If the install method exists, load it 43 | if (method_exists($classname, 'install')) { 44 | $classname::install($migration); 45 | } 46 | } 47 | } 48 | $migration->executeMigration(); 49 | 50 | return true; 51 | } 52 | 53 | function plugin_oauthimap_uninstall() 54 | { 55 | $migration = new Migration(PLUGIN_OAUTHIMAP_VERSION); 56 | 57 | // Parse inc directory 58 | foreach (glob(dirname(__FILE__) . '/inc/*') as $filepath) { 59 | // Load *.class.php files and get the class name 60 | if (preg_match("/inc.(.+)\.class.php/", $filepath, $matches)) { 61 | $classname = 'PluginOauthimap' . ucfirst($matches[1]); 62 | include_once($filepath); 63 | // If the install method exists, load it 64 | if (method_exists($classname, 'uninstall')) { 65 | $classname::uninstall($migration); 66 | } 67 | } 68 | } 69 | 70 | return true; 71 | } 72 | 73 | function plugin_oauthimap_getDropdown() 74 | { 75 | $plugin = new Plugin(); 76 | 77 | if ($plugin->isActivated('oauthimap')) { 78 | return [ 79 | 'PluginOauthimapApplication' => PluginOauthimapApplication::getTypeName(Session::getPluralNumber()), 80 | ]; 81 | } 82 | 83 | return []; 84 | } 85 | -------------------------------------------------------------------------------- /inc/application.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | use GlpiPlugin\Oauthimap\MailCollectorFeature; 32 | use GlpiPlugin\Oauthimap\Provider\Azure; 33 | use GlpiPlugin\Oauthimap\Provider\Google; 34 | use GlpiPlugin\Oauthimap\Provider\ProviderInterface; 35 | use League\OAuth2\Client\Provider\AbstractProvider; 36 | 37 | class PluginOauthimapApplication extends CommonDropdown 38 | { 39 | public static $rightname = 'config'; 40 | 41 | public static function getTypeName($nb = 0) 42 | { 43 | return _n('Oauth IMAP application', 'Oauth IMAP applications', $nb, 'oauthimap'); 44 | } 45 | 46 | public static function getMenuContent() 47 | { 48 | $menu = []; 49 | if (Config::canUpdate()) { 50 | $menu['title'] = self::getMenuName(); 51 | $menu['page'] = '/' . Plugin::getWebDir('oauthimap', false) . '/front/application.php'; 52 | $menu['icon'] = self::getIcon(); 53 | } 54 | if (count($menu)) { 55 | return $menu; 56 | } 57 | 58 | return false; 59 | } 60 | 61 | public static function getIcon() 62 | { 63 | return 'fas fa-sign-in-alt'; 64 | } 65 | 66 | public static function canCreate() 67 | { 68 | return static::canUpdate(); 69 | } 70 | 71 | public static function canPurge() 72 | { 73 | return static::canUpdate(); 74 | } 75 | 76 | public function getAdditionalFields() 77 | { 78 | return [ 79 | [ 80 | 'name' => 'is_active', 81 | 'label' => __('Active'), 82 | 'type' => 'bool', 83 | ], 84 | [ 85 | 'name' => 'provider', 86 | 'label' => __('Oauth provider', 'oauthimap'), 87 | 'type' => 'oauth_provider', 88 | 'list' => true, 89 | ], 90 | [ 91 | 'name' => 'client_id', 92 | 'label' => __('Client ID', 'oauthimap'), 93 | 'type' => 'text', 94 | 'list' => true, 95 | ], 96 | [ 97 | 'name' => 'client_secret', 98 | 'label' => __('Client secret', 'oauthimap'), 99 | 'type' => 'secured_field', 100 | 'list' => false, 101 | ], 102 | [ 103 | 'name' => 'tenant_id', 104 | 'label' => __('Tenant ID', 'oauthimap'), 105 | 'type' => 'additionnal_param', 106 | 'list' => false, 107 | 'provider' => Azure::class, 108 | ], 109 | ]; 110 | } 111 | 112 | public function rawSearchOptions() 113 | { 114 | $tab = parent::rawSearchOptions(); 115 | 116 | $tab[] = [ 117 | 'id' => '5', 118 | 'table' => $this->getTable(), 119 | 'field' => 'provider', 120 | 'name' => __('Oauth provider', 'oauthimap'), 121 | 'searchtype' => ['equals', 'notequals'], 122 | 'datatype' => 'specific', 123 | ]; 124 | 125 | $tab[] = [ 126 | 'id' => '6', 127 | 'table' => $this->getTable(), 128 | 'field' => 'client_id', 129 | 'name' => __('Client ID', 'oauthimap'), 130 | 'datatype' => 'text', 131 | ]; 132 | 133 | $tab[] = [ 134 | 'id' => '7', 135 | 'table' => $this->getTable(), 136 | 'field' => 'tenant_id', 137 | 'name' => __('Tenant ID', 'oauthimap'), 138 | 'datatype' => 'text', 139 | ]; 140 | 141 | return $tab; 142 | } 143 | 144 | public function defineTabs($options = []) 145 | { 146 | $tabs = parent::defineTabs($options); 147 | 148 | $this->addStandardTab(MailCollectorFeature::class, $tabs, $options); 149 | $this->addStandardTab(PluginOauthimapAuthorization::class, $tabs, $options); 150 | 151 | return $tabs; 152 | } 153 | 154 | public function displaySpecificTypeField($ID, $field = [], array $options = []) 155 | { 156 | $rand = sprintf('oauthimap-application-%s', (int) $ID); 157 | 158 | $field_name = $field['name']; 159 | $field_type = $field['type']; 160 | $field_value = $this->fields[$field_name]; 161 | 162 | switch ($field_type) { 163 | case 'oauth_provider': 164 | $values = []; 165 | $icons = []; 166 | foreach (self::getSupportedProviders() as $provider_class) { 167 | $values[$provider_class] = $provider_class::getName(); 168 | $icons[$provider_class] = $provider_class::getIcon(); 169 | } 170 | Dropdown::showFromArray( 171 | $field_name, 172 | $values, 173 | [ 174 | 'display_emptychoice' => true, 175 | 'rand' => $rand, 176 | 'value' => $field_value, 177 | ], 178 | ); 179 | 180 | echo ''; 181 | echo ''; 182 | echo ''; 183 | 184 | $json_icons = json_encode($icons); 185 | $js = << ' + item.text + ''); 193 | }; 194 | 195 | $("#dropdown_{$field_name}{$rand}").select2({ 196 | dropdownAutoWidth: true, 197 | templateSelection: displayOptionIcon, 198 | templateResult: displayOptionIcon, 199 | width: '' 200 | }); 201 | }); 202 | JAVASCRIPT; 203 | echo Html::scriptBlock($js); 204 | break; 205 | case 'secured_field': 206 | echo Html::input( 207 | $field_name, 208 | [ 209 | 'autocomplete' => 'off', 210 | 'value' => Html::entities_deep((new GLPIKey())->decrypt($field_value)), 211 | ], 212 | ); 213 | break; 214 | case 'additionnal_param': 215 | echo Html::input( 216 | $field_name, 217 | [ 218 | 'data-provider' => $field['provider'], 219 | 'value' => $field_value, 220 | ], 221 | ); 222 | break; 223 | default: 224 | throw new \RuntimeException(sprintf('Unknown type %s.', $field_type)); 225 | } 226 | } 227 | 228 | public static function getSpecificValueToDisplay($field, $values, array $options = []) 229 | { 230 | if (!is_array($values)) { 231 | $values = [$field => $values]; 232 | } 233 | 234 | switch ($field) { 235 | case 'provider': 236 | $value = $values[$field]; 237 | if (in_array($value, self::getSupportedProviders())) { 238 | return ' ' . $value::getName(); 239 | } 240 | 241 | return $value; 242 | } 243 | 244 | return parent::getSpecificValueToDisplay($field, $values, $options); 245 | } 246 | 247 | public static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) 248 | { 249 | if (!is_array($values)) { 250 | $values = [$field => $values]; 251 | } 252 | 253 | switch ($field) { 254 | case 'provider': 255 | $selected = ''; 256 | $elements = ['' => Dropdown::EMPTY_VALUE]; 257 | foreach (self::getSupportedProviders() as $class) { 258 | $elements[$class] = $class::getName(); 259 | if ($class === $values[$field]) { 260 | $selected = $class; 261 | } 262 | } 263 | 264 | return Dropdown::showFromArray( 265 | $name, 266 | $elements, 267 | [ 268 | 'display' => false, 269 | 'value' => $selected, 270 | ], 271 | ); 272 | } 273 | 274 | return parent::getSpecificValueToSelect($field, $name, $values, $options); 275 | } 276 | 277 | /** 278 | * Displays form extra fields/scripts. 279 | * 280 | * @param int $id 281 | * 282 | * @return void 283 | */ 284 | public static function showFormExtra(int $id): void 285 | { 286 | $rand = sprintf('oauthimap-application-%s', $id); 287 | 288 | $documentation_urls_json = json_encode(self::getProvidersDocumentationUrls()); 289 | 290 | // Display/hide additionnal params and update documentation link depending on selected provider 291 | $additionnal_params_js = <<'; 319 | echo Html::scriptBlock($additionnal_params_js); 320 | echo ''; 324 | echo '
'; 325 | echo '
'; 326 | echo Html::input( 327 | '', 328 | [ 329 | 'value' => self::getCallbackUrl(), 330 | 'readonly' => 'readonly', 331 | ], 332 | ); 333 | echo ''; 334 | echo '
'; 335 | echo '
'; 336 | echo ''; 337 | } 338 | 339 | public function prepareInputForAdd($input) 340 | { 341 | if (!($input = $this->prepareInput($input))) { 342 | return false; 343 | } 344 | 345 | return parent::prepareInputForAdd($input); 346 | } 347 | 348 | public function prepareInputForUpdate($input) 349 | { 350 | // Unset encrypted fields input if corresponding to current value 351 | // (encryption produces a different value each time, 352 | // so GLPI will consider them as updated on each form submit) 353 | foreach (['client_secret'] as $field_name) { 354 | if ( 355 | array_key_exists($field_name, $input) 356 | && !empty($input[$field_name]) && $input[$field_name] !== 'NULL' 357 | && $input[$field_name] === (new GLPIKey())->decrypt($this->fields[$field_name]) 358 | ) { 359 | unset($input[$field_name]); 360 | } 361 | } 362 | 363 | if (!($input = $this->prepareInput($input))) { 364 | return false; 365 | } 366 | 367 | return parent::prepareInputForUpdate($input); 368 | } 369 | 370 | /** 371 | * Encrypt values of secured fields. 372 | * 373 | * @param array $input 374 | * 375 | * @return bool|array 376 | */ 377 | private function prepareInput($input) 378 | { 379 | if (array_key_exists('name', $input) && empty(trim($input['name']))) { 380 | Session::addMessageAfterRedirect(__('Name cannot be empty', 'oauthimap'), false, ERROR); 381 | 382 | return false; 383 | } 384 | 385 | if ( 386 | array_key_exists('provider', $input) 387 | && !in_array($input['provider'], self::getSupportedProviders()) 388 | ) { 389 | Session::addMessageAfterRedirect(__('Invalid provider', 'oauthimap'), false, ERROR); 390 | 391 | return false; 392 | } 393 | 394 | foreach (['client_secret'] as $field_name) { 395 | if ( 396 | array_key_exists($field_name, $input) 397 | && !empty($input[$field_name]) && $input[$field_name] !== 'NULL' 398 | ) { 399 | $input[$field_name] = (new GLPIKey())->encrypt($input[$field_name]); 400 | } 401 | } 402 | 403 | return $input; 404 | } 405 | 406 | // phpcs:ignore PSR1.Methods.CamelCapsMethodName 407 | public function pre_updateInDB() 408 | { 409 | if ( 410 | in_array('provider', $this->updates) 411 | || in_array('client_id', $this->updates) 412 | || in_array('client_secret', $this->updates) 413 | ) { 414 | // Remove codes and tokens if any credentials parameter changed 415 | $this->deleteChildrenAndRelationsFromDb( 416 | [ 417 | PluginOauthimapAuthorization::class, 418 | ], 419 | ); 420 | } 421 | } 422 | 423 | // phpcs:ignore PSR1.Methods.CamelCapsMethodName 424 | public function post_updateItem($history = true) 425 | { 426 | if (in_array('is_active', $this->updates) && !$this->fields['is_active']) { 427 | MailCollectorFeature::postDeactivateApplication($this); 428 | } 429 | } 430 | 431 | /** 432 | * Redirect to authorization URL corresponding to credentials. 433 | * 434 | * @param callable|null $callback_callable Callable to call on authorization callback 435 | * @param array $callback_params Parameters to pass to callable 436 | * 437 | * @return void 438 | */ 439 | public function redirectToAuthorizationUrl(?callable $callback_callable = null, array $callback_params = []): void 440 | { 441 | if (!$this->areCredentialsValid()) { 442 | throw new \RuntimeException('Invalid credentials.'); 443 | } 444 | 445 | $provider = $this->getProvider(); 446 | 447 | $options = [ 448 | 'scope' => self::getProviderScopes($this->fields['provider']), 449 | ]; 450 | switch ($this->fields['provider']) { 451 | case Azure::class: 452 | $options['prompt'] = 'login'; 453 | break; 454 | case Google::class: 455 | $options['prompt'] = 'consent select_account'; 456 | break; 457 | } 458 | 459 | $auth_url = $provider->getAuthorizationUrl($options); 460 | 461 | $_SESSION['oauth2state'] = $provider->getState(); 462 | $_SESSION[$this->getForeignKeyField()] = $this->fields['id']; 463 | 464 | $_SESSION['plugin_oauthimap_callback_callable'] = $callback_callable; 465 | $_SESSION['plugin_oauthimap_callback_params'] = $callback_params; 466 | 467 | Html::redirect($auth_url); 468 | } 469 | 470 | /** 471 | * Check if credentials are valid (i.e. all fields are correclty set). 472 | * 473 | * @return bool 474 | */ 475 | private function areCredentialsValid(): bool 476 | { 477 | return !$this->isNewItem() 478 | && array_key_exists('provider', $this->fields) 479 | && in_array($this->fields['provider'], self::getSupportedProviders()) 480 | && array_key_exists('client_id', $this->fields) 481 | && !empty($this->fields['client_id']) 482 | && array_key_exists('client_secret', $this->fields) 483 | && !empty($this->fields['client_secret']); 484 | } 485 | 486 | /** 487 | * Get list of supported providers classnames. 488 | * 489 | * @return array 490 | */ 491 | private static function getSupportedProviders(): array 492 | { 493 | return [ 494 | Azure::class, 495 | Google::class, 496 | ]; 497 | } 498 | 499 | /** 500 | * Returns oauth provider class instance. 501 | * 502 | * @return AbstractProvider|ProviderInterface|null 503 | */ 504 | public function getProvider() 505 | { 506 | /** @var array $CFG_GLPI */ 507 | global $CFG_GLPI; 508 | 509 | if (!$this->areCredentialsValid()) { 510 | throw new \RuntimeException('Invalid credentials.'); 511 | } 512 | 513 | if (!is_a($this->fields['provider'], ProviderInterface::class, true)) { 514 | throw new \RuntimeException(sprintf('Unknown provider %s.', $this->fields['provider'])); 515 | } 516 | 517 | $params = [ 518 | 'clientId' => $this->fields['client_id'], 519 | 'clientSecret' => (new GLPIKey())->decrypt($this->fields['client_secret']), 520 | 'redirectUri' => $this->getCallbackUrl(), 521 | 'scope' => self::getProviderScopes($this->fields['provider']), 522 | ]; 523 | 524 | if (!empty($CFG_GLPI['proxy_name'])) { 525 | // Connection using proxy 526 | $params['proxy'] = !empty($CFG_GLPI['proxy_user']) 527 | ? sprintf( 528 | '%s:%s@%s:%s', 529 | rawurlencode($CFG_GLPI['proxy_user']), 530 | rawurlencode((new GLPIKey())->decrypt($CFG_GLPI['proxy_passwd'])), 531 | $CFG_GLPI['proxy_name'], 532 | $CFG_GLPI['proxy_port'], 533 | ) 534 | : sprintf( 535 | '%s:%s', 536 | $CFG_GLPI['proxy_name'], 537 | $CFG_GLPI['proxy_port'], 538 | ); 539 | } 540 | 541 | // Specific parameters 542 | switch ($this->fields['provider']) { 543 | case Azure::class: 544 | $params['defaultEndPointVersion'] = '2.0'; 545 | if (!empty($this->fields['tenant_id'])) { 546 | $params['tenant'] = $this->fields['tenant_id']; 547 | } 548 | break; 549 | case Google::class: 550 | $params['accessType'] = 'offline'; 551 | break; 552 | } 553 | 554 | return new $this->fields['provider']($params); 555 | } 556 | 557 | /** 558 | * Get required scopes for given provider. 559 | * 560 | * @param string $provider Provider classname 561 | * 562 | * @return array 563 | */ 564 | private static function getProviderScopes(string $provider): array 565 | { 566 | $scopes = []; 567 | 568 | switch ($provider) { 569 | case Azure::class: 570 | $scopes = [ 571 | 'openid', 'email', // required to be able to fetch owner details 572 | 'offline_access', 573 | 'https://outlook.office.com/IMAP.AccessAsUser.All', 574 | ]; 575 | break; 576 | case Google::class: 577 | $scopes = [ 578 | 'https://mail.google.com/', 579 | ]; 580 | break; 581 | } 582 | 583 | return $scopes; 584 | } 585 | 586 | /** 587 | * Get documentation URLs. 588 | * Keys are providers classnames, values are URL. 589 | * 590 | * @return array 591 | */ 592 | private static function getProvidersDocumentationUrls(): array 593 | { 594 | return [ 595 | Azure::class => 'https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth', 596 | Google::class => 'https://developers.google.com/gmail/imap/xoauth2-protocol', 597 | ]; 598 | } 599 | 600 | /** 601 | * Get callback URL used during authorization process. 602 | * 603 | * @return string 604 | */ 605 | private static function getCallbackUrl(): string 606 | { 607 | return Plugin::getWebDir('oauthimap', true, true) . '/front/authorization.callback.php'; 608 | } 609 | 610 | public function cleanDBonPurge() 611 | { 612 | $this->deleteChildrenAndRelationsFromDb( 613 | [ 614 | PluginOauthimapAuthorization::class, 615 | ], 616 | ); 617 | } 618 | 619 | /** 620 | * Install all necessary data for this class. 621 | */ 622 | public static function install(Migration $migration) 623 | { 624 | /** @var \DBmysql $DB */ 625 | global $DB; 626 | 627 | $default_charset = DBConnection::getDefaultCharset(); 628 | $default_collation = DBConnection::getDefaultCollation(); 629 | $default_key_sign = DBConnection::getDefaultPrimaryKeySignOption(); 630 | 631 | $table = self::getTable(); 632 | 633 | if (!$DB->tableExists($table)) { 634 | $migration->displayMessage("Installing $table"); 635 | 636 | $query = <<doQuery($query); 656 | } 657 | 658 | // Add display preferences 659 | $migration->updateDisplayPrefs( 660 | [ 661 | 'PluginOauthimapApplication' => [1, 5, 6, 7, 121, 19], 662 | ], 663 | ); 664 | } 665 | 666 | /** 667 | * Uninstall previously installed data for this class. 668 | */ 669 | public static function uninstall(Migration $migration) 670 | { 671 | $table = self::getTable(); 672 | $migration->displayMessage("Uninstalling $table"); 673 | $migration->dropTable($table); 674 | } 675 | } 676 | -------------------------------------------------------------------------------- /inc/authorization.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | use GlpiPlugin\Oauthimap\MailCollectorFeature; 32 | use GlpiPlugin\Oauthimap\Oauth\OwnerDetails; 33 | use League\OAuth2\Client\Token\AccessToken; 34 | use GlpiPlugin\Oauthimap\Imap\ImapOauthProtocol; 35 | use GlpiPlugin\Oauthimap\Imap\ImapOauthStorage; 36 | 37 | class PluginOauthimapAuthorization extends CommonDBChild 38 | { 39 | // From CommonGlpi 40 | protected $displaylist = false; 41 | 42 | // From CommonDBTM 43 | public $dohistory = true; 44 | 45 | // From CommonDBChild 46 | public static $itemtype = 'PluginOauthimapApplication'; 47 | public static $items_id = 'plugin_oauthimap_applications_id'; 48 | 49 | /** 50 | * Authorization owner details. 51 | * @var OwnerDetails 52 | */ 53 | private $owner_details; 54 | 55 | public static function getTypeName($nb = 0) 56 | { 57 | return _n('Oauth authorization', 'Oauth authorizations', $nb, 'oauthimap'); 58 | } 59 | 60 | public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) 61 | { 62 | $count = 0; 63 | if ($_SESSION['glpishow_count_on_tabs'] && $item instanceof PluginOauthimapApplication) { 64 | $count = countElementsInTable( 65 | $this->getTable(), 66 | [ 67 | PluginOauthimapApplication::getForeignKeyField() => $item->getID(), 68 | ], 69 | ); 70 | } 71 | 72 | return self::createTabEntry(self::getTypeName(1), $count); 73 | } 74 | 75 | public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) 76 | { 77 | if (!($item instanceof PluginOauthimapApplication)) { 78 | return false; 79 | } 80 | 81 | /** @var \DBmysql $DB */ 82 | global $DB; 83 | 84 | $iterator = $DB->request( 85 | [ 86 | 'FROM' => self::getTable(), 87 | 'WHERE' => [ 88 | PluginOauthimapApplication::getForeignKeyField() => $item->getID(), 89 | ], 90 | ], 91 | ); 92 | 93 | $item->showFormHeader([ 94 | 'formtitle' => $item->fields['name'], 95 | 'target' => Plugin::getWebDir('oauthimap') . '/front/application.form.php', 96 | ]); 97 | 98 | echo '
'; 99 | echo '
'; 100 | echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); 101 | echo Html::hidden('id', ['value' => $item->getID()]); 102 | echo ''; 105 | echo '
'; 106 | echo '
'; 107 | 108 | echo ''; // #mainformtable 109 | echo ''; // [name=asset_form] 110 | 111 | echo ''; 112 | if ($iterator->count() === 0) { 113 | echo ''; 114 | } else { 115 | echo ''; 116 | echo ''; 117 | echo ''; 118 | echo ''; 119 | echo ''; 120 | echo ''; 121 | 122 | echo ''; 123 | foreach ($iterator as $row) { 124 | echo ''; 125 | 126 | echo ''; 127 | 128 | echo ''; 155 | 156 | echo ''; 157 | } 158 | echo ''; 159 | } 160 | echo '
' . __('No authorizations.', 'oauthimap') . '
' . __('Email', 'oauthimap') . '
' . $row['email'] . ''; 129 | $modal_id = 'plugin_oauthimap_authorization_diagnostic_' . mt_rand(); 130 | Ajax::createIframeModalWindow( 131 | $modal_id, 132 | self::getFormURLWithID($row['id']) . '&diagnose', 133 | [ 134 | 'title' => __('Connection diagnostic', 'oauthimap'), 135 | 'height' => 650, 136 | ], 137 | ); 138 | echo ''; 139 | echo ' ' . __('Diagnose', 'oauthimap'); 140 | echo ''; 141 | echo ' '; 142 | echo ''; 143 | echo ' ' . __('Update', 'oauthimap'); 144 | echo ''; 145 | echo ' '; 146 | echo '
'; 147 | echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); 148 | echo Html::hidden('id', ['value' => $row['id']]); 149 | echo ''; 153 | echo '
'; 154 | echo '
'; 161 | 162 | return true; 163 | } 164 | 165 | public function showForm($id, $options = []) 166 | { 167 | $options['colspan'] = 1; 168 | 169 | $this->initForm($id, $options); 170 | $this->showFormHeader($options); 171 | 172 | echo ''; 173 | echo ''; 174 | echo __('Email', 'oauthimap'); 175 | echo ' '; 176 | echo Html::showToolTip( 177 | __('This email address corresponds to the "user" field of the SASL XOAUTH2 authentication query.'), 178 | ['display' => false], 179 | ); 180 | echo ''; 181 | echo ''; 182 | echo Html::input( 183 | 'email', 184 | [ 185 | 'value' => $this->fields['email'], 186 | 'style' => 'width:90%', 187 | ], 188 | ); 189 | echo ''; 190 | echo ''; 191 | 192 | $this->showFormButtons($options + ['candel' => false]); 193 | 194 | return true; 195 | } 196 | 197 | /** 198 | * Displays diagnostic form. 199 | * 200 | * @param array $params 201 | * 202 | * @return void 203 | */ 204 | public function showDiagnosticForm(array $params) 205 | { 206 | $application = new PluginOauthimapApplication(); 207 | if ( 208 | !$application->getFromDB($this->fields[PluginOauthimapApplication::getForeignKeyField()]) 209 | || ($provider = $application->getProvider()) === null 210 | ) { 211 | return; 212 | } 213 | 214 | $user = $params['user'] ?? $this->fields['email']; 215 | $host = $params['host'] ?? $provider->getDefaultHost(); 216 | $port = (int) ($params['port'] ?? $provider->getDefaultPort()); 217 | $ssl = $params['ssl'] ?? $provider->getDefaultSslFlag(); 218 | $timeout = (int) ($params['timeout'] ?? 2); // 2 seconds timeout by default 219 | 220 | echo '
'; 221 | 222 | echo ''; 223 | echo ''; 224 | echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); 225 | 226 | echo ''; 227 | 228 | echo ''; 229 | echo ''; 232 | echo ''; 241 | echo ''; 242 | 243 | echo ''; 244 | echo ''; 247 | echo ''; 255 | echo ''; 258 | echo ''; 269 | echo ''; 270 | 271 | echo ''; 272 | echo ''; 275 | echo ''; 289 | echo ''; 292 | echo ''; 304 | echo ''; 305 | 306 | echo ''; 307 | echo ''; 316 | echo ''; 317 | 318 | echo ''; 319 | echo ''; 345 | echo ''; 346 | 347 | echo '
'; 230 | echo __('Email', 'oauthimap'); 231 | echo ''; 233 | echo Html::input( 234 | 'email', 235 | [ 236 | 'disabled' => 'disabled', 237 | 'value' => $user, 238 | ], 239 | ); 240 | echo '
'; 245 | echo __('Server host', 'oauthimap'); 246 | echo ''; 248 | echo Html::input( 249 | 'host', 250 | [ 251 | 'value' => $host, 252 | ], 253 | ); 254 | echo ''; 256 | echo __('Server port', 'oauthimap'); 257 | echo ''; 259 | echo Html::input( 260 | 'port', 261 | [ 262 | 'type' => 'integer', 263 | 'min' => 1, 264 | 'value' => $port, 265 | 'size' => 5, 266 | ], 267 | ); 268 | echo '
'; 273 | echo __('Security level', 'oauthimap'); 274 | echo ''; 276 | echo Html::select( 277 | 'ssl', 278 | [ 279 | '' => '', 280 | 'SSL' => __('SSL', 'oauthimap'), 281 | 'TLS' => __('SSL + TLS', 'oauthimap'), 282 | ], 283 | [ 284 | 'selected' => $ssl, 285 | 'class' => 'form-select', 286 | ], 287 | ); 288 | echo ''; 290 | echo __('Timeout', 'oauthimap'); 291 | echo ''; 293 | echo Html::input( 294 | 'timeout', 295 | [ 296 | 'type' => 'integer', 297 | 'min' => 1, 298 | 'max' => 30, 299 | 'value' => $timeout, 300 | 'size' => 5, 301 | ], 302 | ); 303 | echo '
'; 308 | echo Html::submit( 309 | __('Refresh connection diagnostic', 'oauthimap'), 310 | [ 311 | 'name' => 'diagnose', 312 | 'class' => 'btn btn-secondary', 313 | ], 314 | ); 315 | echo '
'; 320 | echo '
'; 321 | echo ''; 322 | echo __('Diagnostic log contains sensitive information, such as the access token.', 'oauthimap'); 323 | echo '
'; 324 | $protocol = new ImapOauthProtocol($application->fields['id']); 325 | $protocol->enableDiagnostic(); 326 | $protocol->setTimeout($timeout); 327 | $error = null; 328 | try { 329 | $protocol->connect($host, $port, $ssl); 330 | if ($protocol->login($user, '')) { 331 | new ImapOauthStorage($protocol); // Will automatically send 'select INBOX'. 332 | } 333 | } catch (\Throwable $e) { 334 | $error = $e; 335 | } 336 | echo '
'; 337 | echo $protocol->getDiagnosticLog(); 338 | echo ''; 339 | if ($error !== null) { 340 | echo '
'; 341 | echo sprintf(__('Unexpected error: %s', 'oauthimap'), $error->getMessage()); 342 | echo '
'; 343 | } 344 | echo '
'; 348 | echo '
'; 349 | } 350 | 351 | public function prepareInputForAdd($input) 352 | { 353 | if (!($input = $this->prepareInput($input))) { 354 | return false; 355 | } 356 | 357 | return parent::prepareInputForAdd($input); 358 | } 359 | 360 | public function prepareInputForUpdate($input) 361 | { 362 | // Unset encrypted fields input if corresponding to current value 363 | // (encryption produces a different value each time, so GLPI will consider them as updated on each form submit) 364 | foreach (['code', 'token', 'refresh_token'] as $field_name) { 365 | if ( 366 | array_key_exists($field_name, $input) 367 | && !empty($input[$field_name]) && $input[$field_name] !== 'NULL' 368 | && $input[$field_name] === (new GLPIKey())->decrypt($this->fields[$field_name]) 369 | ) { 370 | unset($input[$field_name]); 371 | } 372 | } 373 | 374 | if (!($input = $this->prepareInput($input))) { 375 | return false; 376 | } 377 | 378 | return parent::prepareInputForUpdate($input); 379 | } 380 | 381 | /** 382 | * Encrypt values of secured fields. 383 | * 384 | * @param array $input 385 | * 386 | * @return array 387 | */ 388 | private function prepareInput($input) 389 | { 390 | foreach (['code', 'token', 'refresh_token'] as $field_name) { 391 | if ( 392 | array_key_exists($field_name, $input) 393 | && !empty($input[$field_name]) && $input[$field_name] !== 'NULL' 394 | ) { 395 | $input[$field_name] = (new GLPIKey())->encrypt($input[$field_name]); 396 | } 397 | } 398 | 399 | return $input; 400 | } 401 | 402 | /** 403 | * Create an authorization based on authorizarion code. 404 | * 405 | * @param int $application_id 406 | * @param string $code 407 | * 408 | * @return bool 409 | */ 410 | public function createFromCode(int $application_id, string $code): bool 411 | { 412 | $application = new PluginOauthimapApplication(); 413 | if (!$application->getFromDB($application_id)) { 414 | return false; 415 | } 416 | 417 | $provider = $application->getProvider(); 418 | 419 | // Get token 420 | try { 421 | $token = $provider->getAccessToken('authorization_code', ['code' => $code]); 422 | } catch (\Throwable $e) { 423 | trigger_error( 424 | sprintf('Error during authorization code fetching: %s', $e->getMessage()), 425 | E_USER_WARNING, 426 | ); 427 | 428 | return false; 429 | } 430 | 431 | // Get user details 432 | /** @var AccessToken $token */ 433 | $this->owner_details = $provider->getOwnerDetails($token); 434 | $email = $this->owner_details->email; 435 | if ($email === null) { 436 | trigger_error('Unable to get user email', E_USER_WARNING); 437 | 438 | return false; 439 | } 440 | 441 | // Save informations 442 | $input = [ 443 | $application->getForeignKeyField() => $application_id, 444 | 'code' => $code, 445 | 'token' => json_encode($token->jsonSerialize()), 446 | 'refresh_token' => $token->getRefreshToken(), 447 | 'email' => $email, 448 | ]; 449 | 450 | $exists = $this->getFromDBByCrit( 451 | [ 452 | $application->getForeignKeyField() => $application_id, 453 | 'email' => $email, 454 | ], 455 | ); 456 | if ($exists) { 457 | return $this->update(['id' => $this->fields['id']] + $input); 458 | } else { 459 | return $this->add($input); 460 | } 461 | } 462 | 463 | /** 464 | * Get a fresh access token related to given email using given application. 465 | * 466 | * @param int $application_id 467 | * @param string $email 468 | * 469 | * @return string|null 470 | */ 471 | public static function getAccessTokenForApplicationAndEmail($application_id, $email): ?string 472 | { 473 | $application = new PluginOauthimapApplication(); 474 | if (!$application->getFromDB($application_id)) { 475 | return null; 476 | } 477 | 478 | $self = new self(); 479 | if (!$self->getFromDBByCrit([$application->getForeignKeyField() => $application_id, 'email' => $email])) { 480 | return null; 481 | } 482 | 483 | try { 484 | $token = new AccessToken(json_decode((new GLPIKey())->decrypt($self->fields['token']), true)); 485 | } catch (\Throwable $e) { 486 | return null; // Field value may be corrupted 487 | } 488 | 489 | if ($token->hasExpired()) { 490 | // Token has expired, refresh it 491 | $refresh_token = (new GLPIKey())->decrypt($self->fields['refresh_token']); 492 | 493 | $provider = $application->getProvider(); 494 | $token = $provider->getAccessToken( 495 | 'refresh_token', 496 | [ 497 | 'refresh_token' => $refresh_token, 498 | ], 499 | ); 500 | 501 | $input = [ 502 | 'id' => $self->fields['id'], 503 | 'token' => json_encode($token->jsonSerialize()), 504 | ]; 505 | if (!empty($token->getRefreshToken()) && $token->getRefreshToken() !== $refresh_token) { 506 | // Update refresh token if a new one has been received in response. 507 | $input['refresh_token'] = $token->getRefreshToken(); 508 | } 509 | 510 | $self->update($input); 511 | } 512 | 513 | return $token->getToken(); 514 | } 515 | 516 | /** 517 | * Get existing access token. 518 | * 519 | * @return AccessToken|null 520 | */ 521 | public function getAccessToken(): ?AccessToken 522 | { 523 | try { 524 | $token = new AccessToken(json_decode((new GLPIKey())->decrypt($this->fields['token']), true)); 525 | } catch (\Throwable $e) { 526 | return null; // Field value may be corrupted 527 | } 528 | 529 | return $token; 530 | } 531 | 532 | /** 533 | * Returns owner details fetched when creating authorization. 534 | * 535 | * @return OwnerDetails|null 536 | */ 537 | public function getOwnerDetails(): ?OwnerDetails 538 | { 539 | return $this->owner_details; 540 | } 541 | 542 | // phpcs:ignore PSR1.Methods.CamelCapsMethodName 543 | public function post_updateItem($history = 1) 544 | { 545 | MailCollectorFeature::postUpdateAuthorization($this); 546 | parent::post_updateItem($history); 547 | } 548 | 549 | // phpcs:ignore PSR1.Methods.CamelCapsMethodName 550 | public function post_purgeItem() 551 | { 552 | MailCollectorFeature::postPurgeAuthorization($this); 553 | } 554 | 555 | /** 556 | * Install all necessary data for this class. 557 | */ 558 | public static function install(Migration $migration) 559 | { 560 | /** @var \DBmysql $DB */ 561 | global $DB; 562 | 563 | $default_charset = DBConnection::getDefaultCharset(); 564 | $default_collation = DBConnection::getDefaultCollation(); 565 | $default_key_sign = DBConnection::getDefaultPrimaryKeySignOption(); 566 | 567 | $table = self::getTable(); 568 | $application_fkey = PluginOauthimapApplication::getForeignKeyField(); 569 | 570 | if (!$DB->tableExists($table)) { 571 | $migration->displayMessage("Installing $table"); 572 | 573 | $query = <<doQuery($query); 591 | } else { 592 | if (!$DB->fieldExists($table, 'refresh_token')) { 593 | // V1.3.1: add new refresh_token field 594 | $migration->addField( 595 | $table, 596 | 'refresh_token', 597 | 'text', 598 | [ 599 | 'after' => 'token', 600 | 'nodefault' => true, 601 | ], 602 | ); 603 | 604 | $iterator = $DB->request(['FROM' => $table]); 605 | foreach ($iterator as $row) { 606 | $token_fields = json_decode((new GLPIKey())->decrypt($row['token']), true); 607 | if (isset($token_fields['refresh_token'])) { 608 | $migration->addPostQuery( 609 | $DB->buildUpdate( 610 | $table, 611 | [ 612 | 'refresh_token' => (new GLPIKey())->encrypt($token_fields['refresh_token']), 613 | ], 614 | [ 615 | 'id' => $row['id'], 616 | ], 617 | ), 618 | ); 619 | } 620 | } 621 | } 622 | } 623 | } 624 | 625 | /** 626 | * Uninstall previously installed data for this class. 627 | */ 628 | public static function uninstall(Migration $migration) 629 | { 630 | $table = self::getTable(); 631 | $migration->displayMessage("Uninstalling $table"); 632 | $migration->dropTable($table); 633 | } 634 | } 635 | -------------------------------------------------------------------------------- /inc/hook.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | use GlpiPlugin\Oauthimap\MailCollectorFeature; 32 | 33 | class PluginOauthimapHook 34 | { 35 | /** 36 | * Handle post_item_form hook. 37 | * 38 | * @param array $params 39 | * 40 | * @return void 41 | */ 42 | public static function postItemForm(array $params): void 43 | { 44 | $item = $params['item']; 45 | 46 | if (!is_object($item)) { 47 | return; 48 | } 49 | 50 | switch (get_class($item)) { 51 | case MailCollector::class: 52 | MailCollectorFeature::alterMailCollectorForm(); 53 | break; 54 | case PluginOauthimapApplication::class: 55 | PluginOauthimapApplication::showFormExtra((int) $item->fields[PluginOauthimapApplication::getIndexName()]); 56 | break; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /inc/imap/imapoauthprotocol.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | namespace GlpiPlugin\Oauthimap\Imap; 32 | 33 | use Glpi\Mail\Protocol\ProtocolInterface; 34 | use Laminas\Mail\Protocol\Imap; 35 | use PluginOauthimapAuthorization; 36 | 37 | class ImapOauthProtocol extends Imap implements ProtocolInterface 38 | { 39 | /** 40 | * Prefix to use when writing a sent line in diagnostic log. 41 | * 42 | * @var string 43 | */ 44 | private const DIAGNOSTIC_PREFIX_SENT = '>>> '; 45 | 46 | /** 47 | * Prefix to use when writing a received line in diagnostic log. 48 | * 49 | * @var string 50 | */ 51 | private const DIAGNOSTIC_PREFIX_RECEIVED = '<<< '; 52 | 53 | /** 54 | * ID of PluginOauthimapApplication to use. 55 | * 56 | * @var int 57 | */ 58 | private $application_id; 59 | 60 | /** 61 | * Indicates whether diagnostic is enabled. 62 | * 63 | * @var boolean 64 | */ 65 | private $diagnostic_enabled = false; 66 | 67 | /** 68 | * Diagnostic log. 69 | * 70 | * @var string[] 71 | */ 72 | private $diagnostic_log = []; 73 | 74 | /** 75 | * Connection timeout. 76 | * 77 | * @var int 78 | */ 79 | private $timeout = self::TIMEOUT_CONNECTION; 80 | 81 | /** 82 | * @param int $application_id ID of PluginOauthimapApplication to use 83 | */ 84 | public function __construct($application_id) 85 | { 86 | $this->application_id = $application_id; 87 | parent::__construct(); 88 | } 89 | 90 | /** 91 | * Almost identical to parent class method, just to be able to redefine timeout in case of diagnostic. 92 | * 93 | * {@inheritDoc} 94 | */ 95 | public function connect($host, $port = null, $ssl = false) 96 | { 97 | $transport = 'tcp'; 98 | $isTls = false; 99 | 100 | if ($ssl) { 101 | $ssl = strtolower($ssl); 102 | } 103 | 104 | switch ($ssl) { 105 | case 'ssl': 106 | $transport = 'ssl'; 107 | if (!$port) { 108 | $port = 993; 109 | } 110 | break; 111 | case 'tls': 112 | $isTls = true; 113 | // break intentionally omitted 114 | // no break 115 | default: 116 | if (!$port) { 117 | $port = 143; 118 | } 119 | } 120 | 121 | $this->socket = $this->setupSocket($transport, $host, $port, $this->timeout); 122 | 123 | if (!$this->assumedNextLine('* OK')) { 124 | throw new \Laminas\Mail\Protocol\Exception\RuntimeException('host doesn\'t allow connection'); 125 | } 126 | 127 | if ($isTls) { 128 | $result = $this->requestAndResponse('STARTTLS'); 129 | /** @phpstan-ignore-next-line */ 130 | $result = $result && stream_socket_enable_crypto($this->socket, true, $this->getCryptoMethod()); 131 | if (!$result) { 132 | throw new \Laminas\Mail\Protocol\Exception\RuntimeException('cannot enable TLS'); 133 | } 134 | } 135 | } 136 | 137 | public function login($user, $password) 138 | { 139 | $token = PluginOauthimapAuthorization::getAccessTokenForApplicationAndEmail($this->application_id, $user); 140 | 141 | if ($token === null) { 142 | trigger_error('Unable to get access token', E_USER_WARNING); 143 | 144 | return false; 145 | } 146 | 147 | $this->sendRequest( 148 | 'AUTHENTICATE', 149 | [ 150 | 'XOAUTH2', 151 | base64_encode("user={$user}\001auth=Bearer {$token}\001\001"), 152 | ], 153 | ); 154 | 155 | while (true) { 156 | $response = ''; 157 | $isPlus = $this->readLine($response, '+', true); 158 | if ($isPlus) { 159 | // Send empty client response. 160 | $this->sendRequest(''); 161 | } else { 162 | if ( 163 | preg_match('/^NO /i', $response) || preg_match('/^BAD /i', $response) 164 | ) { 165 | return false; 166 | } 167 | if (preg_match('/^OK /i', $response)) { 168 | return true; 169 | } 170 | } 171 | } 172 | } 173 | 174 | /** 175 | * Almost identical to parent class method, some `$this->addToDiagnosticLog()` calls were added. 176 | * 177 | * {@inheritDoc} 178 | */ 179 | public function sendRequest($command, $tokens = [], &$tag = '') 180 | { 181 | if (!$tag) { 182 | ++$this->tagCount; 183 | $tag = 'TAG' . $this->tagCount; 184 | } 185 | 186 | $line = $tag . ' ' . $command; 187 | 188 | foreach ($tokens as $token) { 189 | if (is_array($token)) { 190 | $tosend = $line . ' ' . $token[0] . "\r\n"; 191 | if (fwrite($this->socket, $tosend) === false) { 192 | throw new \Laminas\Mail\Protocol\Exception\RuntimeException('cannot write - connection closed?'); 193 | } 194 | $this->addToDiagnosticLog($tosend, self::DIAGNOSTIC_PREFIX_SENT); 195 | if (!$this->assumedNextLine('+ ')) { 196 | throw new \Laminas\Mail\Protocol\Exception\RuntimeException('cannot send literal string'); 197 | } 198 | $line = $token[1]; 199 | } else { 200 | $line .= ' ' . $token; 201 | } 202 | } 203 | 204 | $tosend = $line . "\r\n"; 205 | if (fwrite($this->socket, $line . "\r\n") === false) { 206 | throw new \Laminas\Mail\Protocol\Exception\RuntimeException('cannot write - connection closed?'); 207 | } 208 | $this->addToDiagnosticLog($tosend, self::DIAGNOSTIC_PREFIX_SENT); 209 | } 210 | 211 | /** 212 | * Almost identical to parent class method, `$this->addToDiagnosticLog()` call added. 213 | * 214 | * {@inheritDoc} 215 | */ 216 | protected function nextLine() 217 | { 218 | $line = fgets($this->socket); 219 | if ($line === false) { 220 | throw new \Laminas\Mail\Protocol\Exception\RuntimeException('cannot read - connection closed?'); 221 | } 222 | $this->addToDiagnosticLog($line, self::DIAGNOSTIC_PREFIX_RECEIVED); 223 | 224 | return $line; 225 | } 226 | 227 | /** 228 | * Enable diagnostic. 229 | * 230 | * @return void 231 | */ 232 | public function enableDiagnostic(): void 233 | { 234 | $this->diagnostic_enabled = true; 235 | } 236 | 237 | /** 238 | * Get the diagnostic log. 239 | * 240 | * @return string 241 | */ 242 | public function getDiagnosticLog(): string 243 | { 244 | return implode('', $this->diagnostic_log); 245 | } 246 | 247 | /** 248 | * Add line to diagnostic log. 249 | * 250 | * @param string $line 251 | * @param string $prefix 252 | * 253 | * @return void 254 | */ 255 | private function addToDiagnosticLog(string $line, string $prefix = '') 256 | { 257 | if (!$this->diagnostic_enabled) { 258 | return; 259 | } 260 | $this->diagnostic_log[] = $prefix . $line; 261 | } 262 | 263 | /** 264 | * Defines socket timeout. 265 | * 266 | * @param int $timeout 267 | * 268 | * @return void 269 | */ 270 | public function setTimeout(int $timeout): void 271 | { 272 | $this->timeout = $timeout; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /inc/imap/imapoauthstorage.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | namespace GlpiPlugin\Oauthimap\Imap; 32 | 33 | use Laminas\Mail\Storage\Imap; 34 | 35 | class ImapOauthStorage extends Imap 36 | { 37 | public function __construct($params) 38 | { 39 | if (is_array($params)) { 40 | $params = (object) $params; 41 | } 42 | 43 | $this->has['flags'] = true; 44 | 45 | if ($params instanceof ImapOauthProtocol) { 46 | $this->protocol = $params; 47 | try { 48 | $this->selectFolder('INBOX'); 49 | } catch (\Laminas\Mail\Storage\Exception\ExceptionInterface $e) { 50 | throw new \Laminas\Mail\Storage\Exception\RuntimeException('cannot select INBOX, is this a valid transport?', 0, $e); 51 | } 52 | 53 | return; 54 | } 55 | 56 | if (!isset($params->application_id)) { 57 | throw new \Laminas\Mail\Storage\Exception\InvalidArgumentException('Oauth credentials must be defined'); 58 | } 59 | 60 | if (!isset($params->user)) { 61 | throw new \Laminas\Mail\Storage\Exception\InvalidArgumentException('need at least user in params'); 62 | } 63 | 64 | $host = isset($params->host) ? $params->host : 'localhost'; 65 | $password = ''; // No password used in Oauth process 66 | $port = isset($params->port) ? $params->port : null; 67 | $ssl = isset($params->ssl) ? $params->ssl : false; 68 | 69 | $this->protocol = new ImapOauthProtocol($params->application_id); 70 | 71 | if (isset($params->novalidatecert)) { 72 | $this->protocol->setNoValidateCert((bool) $params->novalidatecert); 73 | } 74 | 75 | $this->protocol->connect($host, $port, $ssl); 76 | if (!$this->protocol->login($params->user, $password)) { 77 | throw new \Laminas\Mail\Storage\Exception\RuntimeException('cannot login, user or password wrong'); 78 | } 79 | $this->selectFolder(isset($params->folder) ? $params->folder : 'INBOX'); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /inc/mailcollectorfeature.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | namespace GlpiPlugin\Oauthimap; 32 | 33 | use CommonGLPI; 34 | use Dropdown; 35 | use GlpiPlugin\Oauthimap\Imap\ImapOauthProtocol; 36 | use GlpiPlugin\Oauthimap\Imap\ImapOauthStorage; 37 | use Html; 38 | use MailCollector; 39 | use Plugin; 40 | use PluginOauthimapApplication; 41 | use PluginOauthimapAuthorization; 42 | use Session; 43 | 44 | class MailCollectorFeature 45 | { 46 | public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) 47 | { 48 | if ($item instanceof PluginOauthimapApplication) { 49 | $count = 0; 50 | if ($_SESSION['glpishow_count_on_tabs']) { 51 | $collectors = MailCollectorFeature::getAssociatedMailCollectors( 52 | MailCollectorFeature::getMailProtocolTypeIdentifier($item->getID()), 53 | null, 54 | false, 55 | ); 56 | $count = count($collectors); 57 | } 58 | 59 | return CommonGLPI::createTabEntry(MailCollector::getTypeName(Session::getPluralNumber()), $count); 60 | } 61 | 62 | return ''; 63 | } 64 | 65 | public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) 66 | { 67 | if ($item instanceof PluginOauthimapApplication) { 68 | MailCollectorFeature::showMailCollectorsForApplication($item); 69 | } 70 | 71 | return false; 72 | } 73 | 74 | /** 75 | * Get mail protocols specs as expected by 'mail_server_protocols' hook. 76 | */ 77 | public static function getMailProtocols() 78 | { 79 | $mail_protocols = []; 80 | 81 | $values = getAllDataFromTable( 82 | PluginOauthimapApplication::getTable(), 83 | [ 84 | 'WHERE' => [ 85 | 'is_active' => 1, 86 | ], 87 | ], 88 | ); 89 | 90 | foreach ($values as $value) { 91 | $id = $value['id']; 92 | $protocol_class = function () use ($id) { 93 | return new ImapOauthProtocol($id); 94 | }; 95 | $storage_class = function (array $params) use ($id) { 96 | $params['application_id'] = $id; 97 | 98 | return new ImapOauthStorage($params); 99 | }; 100 | $mail_protocols[self::getMailProtocolTypeIdentifier($id)] = [ 101 | 'label' => $value['name'], 102 | 'protocol' => $protocol_class, 103 | 'storage' => $storage_class, 104 | ]; 105 | } 106 | 107 | return $mail_protocols; 108 | } 109 | 110 | /** 111 | * Return mail protocol type identifier. 112 | * 113 | * @param int $application_id 114 | * 115 | * @return string 116 | */ 117 | public static function getMailProtocolTypeIdentifier($application_id) 118 | { 119 | return sprintf('imap-oauth-%s', $application_id); 120 | } 121 | 122 | /** 123 | * Alter MailCollector form in order to handle IMAP Oauth connections. 124 | * 125 | * @return void 126 | */ 127 | public static function alterMailCollectorForm(): void 128 | { 129 | $locator_id = 'plugin_oauthimap_locator_' . mt_rand(); 130 | $plugin_path = Plugin::getWebDir('oauthimap'); 131 | 132 | echo ''; 133 | $javascript = <<'); 143 | var application_field = form.find('[name="plugin_oauthimap_applications_id"]'); 144 | 145 | login_field.parent().append(''); 146 | var auth_field_container = $('#auth_field_container'); 147 | 148 | server_type_field.on( 149 | 'change', 150 | function (evt) { 151 | if (/^\/imap-oauth-\d+$/.test($(this).val())) { 152 | var application_id = $(this).val().replace('/imap-oauth-', ''); 153 | 154 | password_field.closest('tr').hide(); 155 | login_field.hide(); 156 | auth_field_container.show(); 157 | 158 | application_field.val(application_id); 159 | auth_field_container.load( 160 | '{$plugin_path}/ajax/dropdownAuthorization.php', 161 | { 162 | application_id: application_id, 163 | selected: login_field.val() 164 | } 165 | ); 166 | } else { 167 | password_field.closest('tr').show(); 168 | auth_field_container.hide(); 169 | login_field.show(); 170 | 171 | application_field.val(''); 172 | } 173 | } 174 | ); 175 | 176 | // Change login field value to trigger mail collector update 177 | auth_field_container.on( 178 | 'change', 179 | 'select', 180 | function (evt) { 181 | if ($(this).val() == -1) { 182 | login_field.val(''); 183 | } else { 184 | login_field.val($(this).find('option:selected').text()); 185 | } 186 | } 187 | ); 188 | 189 | server_type_field.trigger('change'); 190 | } 191 | ); 192 | JAVASCRIPT; 193 | 194 | echo Html::scriptBlock($javascript); 195 | } 196 | 197 | /** 198 | * Force mailcollector update if oauth fields should trigger an authorization request. 199 | * 200 | * @param MailCollector $item 201 | * 202 | * @return boolean 203 | */ 204 | public static function forceMailCollectorUpdate(MailCollector $item) 205 | { 206 | if ( 207 | !array_key_exists('plugin_oauthimap_applications_id', $item->input) 208 | || !array_key_exists('plugin_oauthimap_authorizations_id', $item->input) 209 | ) { 210 | // Plugin fields are not present, update was not made inside form. 211 | return true; 212 | } 213 | 214 | if (!($item->input['plugin_oauthimap_applications_id'] > 0)) { 215 | // No application selected => mail collector does not use Oauth. 216 | // Return true to continue update. 217 | return true; 218 | } 219 | if ($item->input['plugin_oauthimap_authorizations_id'] > 0) { 220 | // Existing authorization selected => no need to trigger authorization request. 221 | // Return true to continue update. 222 | return true; 223 | } 224 | 225 | // Defines "date_mod" field of mail collector to force its update. 226 | // Indeed, if no mail collector field changed, "item_update" hook will not be called and authorization request 227 | // will not be triggered. 228 | $item->input['date_mod'] = $_SESSION['glpi_currenttime']; 229 | 230 | return true; 231 | } 232 | 233 | /** 234 | * Handle authorization process after creation/update of a mail collector. 235 | * 236 | * @param MailCollector $item 237 | * 238 | * @return void 239 | */ 240 | public static function handleMailCollectorSaving(MailCollector $item): void 241 | { 242 | if ( 243 | !array_key_exists('plugin_oauthimap_applications_id', $item->input) 244 | || !array_key_exists('plugin_oauthimap_authorizations_id', $item->input) 245 | ) { 246 | // Plugin fields are not present, update was not made inside form. 247 | return; 248 | } 249 | 250 | $applications_id = $item->input['plugin_oauthimap_applications_id']; 251 | $authorizations_id = $item->input['plugin_oauthimap_authorizations_id']; 252 | 253 | if (!($applications_id > 0)) { 254 | // No application selected => mail collector does not use Oauth. 255 | return; 256 | } 257 | 258 | $application = new PluginOauthimapApplication(); 259 | $application->getFromDB($applications_id); 260 | $authorization = new PluginOauthimapAuthorization(); 261 | 262 | if ($authorizations_id > 0 && $authorization->getFromDB($authorizations_id)) { 263 | // Use existing authorization 264 | self::updateMailCollectorOnAuthorizationCallback( 265 | true, 266 | $authorization, 267 | [ 268 | MailCollector::getForeignKeyField() => $item->getID(), 269 | ], 270 | ); 271 | } else { 272 | // Create new authorization 273 | $application->redirectToAuthorizationUrl( 274 | [self::class, 'updateMailCollectorOnAuthorizationCallback'], 275 | [ 276 | MailCollector::getForeignKeyField() => $item->getID(), 277 | ], 278 | ); 279 | } 280 | } 281 | 282 | /** 283 | * Update login field of mail collector on authorization callback. 284 | * 285 | * @param bool $success 286 | * @param PluginOauthimapAuthorization $authorization 287 | * @param array $params 288 | * 289 | * @return void 290 | */ 291 | public static function updateMailCollectorOnAuthorizationCallback( 292 | bool $success, 293 | PluginOauthimapAuthorization $authorization, 294 | array $params = [] 295 | ): void { 296 | $mailcollector = new MailCollector(); 297 | $redirect = $mailcollector->getSearchURL(); 298 | if ($success) { 299 | // Store authorized email into MailCollector 300 | $mailcollector_id = $params[$mailcollector->getForeignKeyField()] ?? null; 301 | if ($mailcollector_id !== null && $mailcollector->getFromDB($mailcollector_id)) { 302 | $mailcollector->update( 303 | [ 304 | 'id' => $mailcollector_id, 305 | 'login' => $authorization->fields['email'], 306 | ], 307 | ); 308 | $redirect = $mailcollector->getLinkURL(); 309 | } 310 | } 311 | 312 | Html::redirect($redirect); 313 | } 314 | 315 | /** 316 | * Deactivate mail collectors linked to the application. 317 | * 318 | * @param PluginOauthimapApplication $application 319 | * 320 | * @return void 321 | */ 322 | public static function postDeactivateApplication(PluginOauthimapApplication $application): void 323 | { 324 | self::deactivateMailCollectors( 325 | self::getMailProtocolTypeIdentifier($application->getID()), 326 | ); 327 | } 328 | 329 | /** 330 | * Deactivate mail collectors linked to the authorization. 331 | * 332 | * @param PluginOauthimapAuthorization $authorization 333 | * 334 | * @return void 335 | */ 336 | public static function postPurgeAuthorization(PluginOauthimapAuthorization $authorization): void 337 | { 338 | $application_id = $authorization->fields[PluginOauthimapApplication::getForeignKeyField()]; 339 | self::deactivateMailCollectors( 340 | self::getMailProtocolTypeIdentifier($application_id), 341 | $authorization->fields['email'], 342 | ); 343 | } 344 | 345 | /** 346 | * Update mail collectors linked to the authorization. 347 | * 348 | * @param PluginOauthimapAuthorization $authorization 349 | * 350 | * @return void 351 | */ 352 | public static function postUpdateAuthorization(PluginOauthimapAuthorization $authorization): void 353 | { 354 | if (in_array('email', $authorization->updates) && array_key_exists('email', $authorization->oldvalues)) { 355 | $collectors = self::getAssociatedMailCollectors( 356 | self::getMailProtocolTypeIdentifier($authorization->fields[PluginOauthimapApplication::getForeignKeyField()]), 357 | $authorization->oldvalues['email'], 358 | ); 359 | foreach ($collectors as $row) { 360 | $mailcollector = new MailCollector(); 361 | $mailcollector->update( 362 | [ 363 | 'id' => $row['id'], 364 | 'login' => $authorization->fields['email'], 365 | ], 366 | ); 367 | Session::addMessageAfterRedirect( 368 | sprintf( 369 | __('Mail receiver "%s" has been updated.', 'oauthimap'), 370 | $mailcollector->getName(), 371 | ), 372 | ); 373 | } 374 | } 375 | } 376 | 377 | /** 378 | * Deactivate mail collectors using given protocol type and given login. 379 | * 380 | * @param string $protocol_type 381 | * @param string $login 382 | * 383 | * @return void 384 | */ 385 | private static function deactivateMailCollectors(string $protocol_type, ?string $login = null) 386 | { 387 | $collectors = self::getAssociatedMailCollectors($protocol_type, $login); 388 | 389 | foreach ($collectors as $row) { 390 | $mailcollector = new MailCollector(); 391 | $mailcollector->update( 392 | [ 393 | 'id' => $row['id'], 394 | 'is_active' => 0, 395 | ], 396 | ); 397 | Session::addMessageAfterRedirect( 398 | sprintf( 399 | __('Mail receiver "%s" has been deactivated.', 'oauthimap'), 400 | $mailcollector->getName(), 401 | ), 402 | ); 403 | } 404 | } 405 | 406 | /** 407 | * Return mail collectors using given protocol type and given login. 408 | * 409 | * @param string $protocol_type 410 | * @param string $login 411 | * @param bool $only_active 412 | * 413 | * @return array 414 | */ 415 | private static function getAssociatedMailCollectors( 416 | string $protocol_type, 417 | string $login = null, 418 | bool $only_active = true 419 | ) { 420 | $criteria = []; 421 | if ($only_active) { 422 | $criteria['is_active'] = 1; 423 | } 424 | if ($login !== null) { 425 | $criteria['login'] = $login; 426 | } 427 | 428 | $data = getAllDataFromTable(MailCollector::getTable(), $criteria); 429 | 430 | $result = []; 431 | 432 | foreach ($data as $row) { 433 | // type follows first found "/" and ends on next "/" (or end of server string) 434 | // server string is surrounded by "{}" and can be followed by a folder name 435 | // i.e. "{mail.domain.org/imap/ssl}INBOX", or "{mail.domain.org/pop}" 436 | // 437 | // see Toolbox::parseMailServerConnectString() 438 | $type = preg_replace('/^\{[^\/]+\/([^\/]+)(?:\/.+)*\}.*/', '$1', $row['host']); 439 | if ($type === $protocol_type) { 440 | $result[] = $row; 441 | } 442 | } 443 | 444 | return $result; 445 | } 446 | 447 | /** 448 | * Display "mail collectors" tab of application page. 449 | * 450 | * @param PluginOauthimapApplication $application 451 | * 452 | * @return void 453 | */ 454 | public static function showMailCollectorsForApplication(PluginOauthimapApplication $application): void 455 | { 456 | $collectors = self::getAssociatedMailCollectors( 457 | self::getMailProtocolTypeIdentifier($application->getID()), 458 | null, 459 | false, 460 | ); 461 | 462 | echo ''; 463 | if (count($collectors) === 0) { 464 | echo ''; 465 | } else { 466 | echo ''; 467 | echo ''; 468 | echo ''; 469 | echo ''; 470 | echo ''; 471 | echo ''; 472 | 473 | foreach ($collectors as $row) { 474 | $mailcollector = new MailCollector(); 475 | $mailcollector->getFromDB($row['id']); 476 | 477 | $name = $mailcollector->canViewItem() ? $mailcollector->getLink() : $mailcollector->getNameID(); 478 | 479 | echo ''; 480 | echo ''; 481 | echo ''; 482 | echo ''; 483 | echo ''; 484 | echo ''; 485 | } 486 | } 487 | echo '
' . __('No associated receivers.', 'oauthimap') . '
' . __('Name', 'oauthimap') . '' . __('Connection string', 'oauthimap') . '' . __('Login', 'oauthimap') . '' . __('Is active ?', 'oauthimap') . '
' . $name . '' . $row['host'] . '' . $row['login'] . '' . Dropdown::getYesNo($row['is_active']) . '
'; 488 | echo ''; 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /inc/oauth/ownerdetails.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | namespace GlpiPlugin\Oauthimap\Oauth; 32 | 33 | class OwnerDetails 34 | { 35 | public $email; 36 | 37 | public $firstname; 38 | 39 | public $lastname; 40 | } 41 | -------------------------------------------------------------------------------- /inc/provider/azure.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | namespace GlpiPlugin\Oauthimap\Provider; 32 | 33 | use GlpiPlugin\Oauthimap\Oauth\OwnerDetails; 34 | use League\OAuth2\Client\Token\AccessToken; 35 | use TheNetworg\OAuth2\Client\Provider\AzureResourceOwner; 36 | 37 | class Azure extends \TheNetworg\OAuth2\Client\Provider\Azure implements ProviderInterface 38 | { 39 | public static function getName(): string 40 | { 41 | return 'Azure'; 42 | } 43 | 44 | public static function getIcon(): string 45 | { 46 | return 'fa-windows'; 47 | } 48 | 49 | public function getOwnerDetails(AccessToken $token): ?OwnerDetails 50 | { 51 | /** @var AzureResourceOwner $owner */ 52 | $owner = $this->getResourceOwner($token); 53 | 54 | $owner_details = new OwnerDetails(); 55 | if (($email = $owner->claim('email')) !== null) { 56 | $owner_details->email = $email; 57 | } elseif (($upn = $owner->claim('upn')) !== null) { 58 | $owner_details->email = $upn; 59 | } 60 | $owner_details->firstname = $owner->getFirstName(); 61 | $owner_details->lastname = $owner->getLastName(); 62 | 63 | return $owner_details; 64 | } 65 | 66 | public function getDefaultHost(): string 67 | { 68 | return 'outlook.office365.com'; 69 | } 70 | 71 | public function getDefaultPort(): ?int 72 | { 73 | return 993; 74 | } 75 | 76 | public function getDefaultSslFlag(): ?string 77 | { 78 | return 'SSL'; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /inc/provider/google.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | namespace GlpiPlugin\Oauthimap\Provider; 32 | 33 | use GlpiPlugin\Oauthimap\Oauth\OwnerDetails; 34 | use League\OAuth2\Client\Token\AccessToken; 35 | use League\OAuth2\Client\Provider\GoogleUser; 36 | 37 | class Google extends \League\OAuth2\Client\Provider\Google implements ProviderInterface 38 | { 39 | public static function getName(): string 40 | { 41 | return 'Google'; 42 | } 43 | 44 | public static function getIcon(): string 45 | { 46 | return 'fa-google'; 47 | } 48 | 49 | public function getOwnerDetails(AccessToken $token): ?OwnerDetails 50 | { 51 | /** @var GoogleUser $owner */ 52 | $owner = $this->getResourceOwner($token); 53 | 54 | $owner_details = new OwnerDetails(); 55 | $owner_details->email = $owner->getEmail(); 56 | $owner_details->firstname = $owner->getFirstName(); 57 | $owner_details->lastname = $owner->getLastName(); 58 | 59 | return $owner_details; 60 | } 61 | 62 | public function getDefaultHost(): string 63 | { 64 | return 'imap.gmail.com'; 65 | } 66 | 67 | public function getDefaultPort(): ?int 68 | { 69 | return 993; 70 | } 71 | 72 | public function getDefaultSslFlag(): ?string 73 | { 74 | return 'SSL'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /inc/provider/providerinterface.class.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | namespace GlpiPlugin\Oauthimap\Provider; 32 | 33 | use GlpiPlugin\Oauthimap\Oauth\OwnerDetails; 34 | use League\OAuth2\Client\Token\AccessToken; 35 | 36 | interface ProviderInterface 37 | { 38 | /** 39 | * Return provider name. 40 | * 41 | * @return string 42 | */ 43 | public static function getName(): string; 44 | 45 | /** 46 | * Return provider icon (Font-Awesome identifier). 47 | * 48 | * @return string 49 | */ 50 | public static function getIcon(): string; 51 | 52 | /** 53 | * Return token owner details. 54 | * 55 | * @param AccessToken $token 56 | * 57 | * @return OwnerDetails|null 58 | */ 59 | public function getOwnerDetails(AccessToken $token): ?OwnerDetails; 60 | 61 | /** 62 | * Returns default host for IMAP connection. 63 | * 64 | * @return string 65 | */ 66 | public function getDefaultHost(): string; 67 | 68 | /** 69 | * Returns default port for IMAP connection. 70 | * 71 | * @return int|null 72 | */ 73 | public function getDefaultPort(): ?int; 74 | 75 | /** 76 | * Returns default SSL flag ('SSL', 'TLS' or null) for IMAP connection. 77 | * 78 | * @return string|null 79 | */ 80 | public function getDefaultSslFlag(): ?string; 81 | } 82 | -------------------------------------------------------------------------------- /locales/en_GB.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/locales/en_GB.mo -------------------------------------------------------------------------------- /locales/en_GB.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Cédric Anne, 2021 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2024-09-12 13:10+0000\n" 15 | "PO-Revision-Date: 2020-10-15 13:49+0000\n" 16 | "Last-Translator: Cédric Anne, 2021\n" 17 | "Language-Team: English (United Kingdom) (https://app.transifex.com/teclib/teams/28042/en_GB/)\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "Language: en_GB\n" 22 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 23 | 24 | #: front/authorization.callback.php:64 25 | #, php-format 26 | msgid "Authorization failed with error: %s" 27 | msgstr "Authorization failed with error: %s" 28 | 29 | #: inc/application.class.php:323 30 | msgid "Callback url" 31 | msgstr "Callback url" 32 | 33 | #: inc/application.class.php:92 inc/application.class.php:129 34 | msgid "Client ID" 35 | msgstr "Client ID" 36 | 37 | #: inc/application.class.php:98 38 | msgid "Client secret" 39 | msgstr "Client secret" 40 | 41 | #: inc/authorization.class.php:134 42 | msgid "Connection diagnostic" 43 | msgstr "Connection diagnostic" 44 | 45 | #: inc/mailcollectorfeature.class.php:471 46 | msgid "Connection string" 47 | msgstr "Connection string" 48 | 49 | #: inc/authorization.class.php:103 50 | msgid "Create an authorization" 51 | msgstr "Create an authorization" 52 | 53 | #: ajax/dropdownAuthorization.php:49 54 | msgid "Create authorization for another user" 55 | msgstr "Create authorization for another user" 56 | 57 | #: inc/authorization.class.php:151 58 | msgid "Delete" 59 | msgstr "Delete" 60 | 61 | #: inc/application.class.php:180 62 | msgid "Developer help for this provider" 63 | msgstr "Developer help for this provider" 64 | 65 | #: inc/authorization.class.php:139 66 | msgid "Diagnose" 67 | msgstr "Diagnose" 68 | 69 | #: inc/authorization.class.php:322 70 | msgid "" 71 | "Diagnostic log contains sensitive information, such as the access token." 72 | msgstr "" 73 | "Diagnostic log contains sensitive information, such as the access token." 74 | 75 | #: inc/authorization.class.php:117 inc/authorization.class.php:174 76 | #: inc/authorization.class.php:230 77 | msgid "Email" 78 | msgstr "Email" 79 | 80 | #: inc/application.class.php:390 81 | msgid "Invalid provider" 82 | msgstr "Invalid provider" 83 | 84 | #: inc/mailcollectorfeature.class.php:473 85 | msgid "Is active ?" 86 | msgstr "Is active ?" 87 | 88 | #: inc/mailcollectorfeature.class.php:472 89 | msgid "Login" 90 | msgstr "Login" 91 | 92 | #: inc/mailcollectorfeature.class.php:402 93 | #, php-format 94 | msgid "Mail receiver \"%s\" has been deactivated." 95 | msgstr "Mail receiver \"%s\" has been deactivated." 96 | 97 | #: inc/mailcollectorfeature.class.php:372 98 | #, php-format 99 | msgid "Mail receiver \"%s\" has been updated." 100 | msgstr "Mail receiver \"%s\" has been updated." 101 | 102 | #: inc/mailcollectorfeature.class.php:470 103 | msgid "Name" 104 | msgstr "Name" 105 | 106 | #: inc/application.class.php:381 107 | msgid "Name cannot be empty" 108 | msgstr "Name cannot be empty" 109 | 110 | #: inc/mailcollectorfeature.class.php:467 111 | msgid "No associated receivers." 112 | msgstr "No associated receivers." 113 | 114 | #: inc/authorization.class.php:113 115 | msgid "No authorizations." 116 | msgstr "No authorizations." 117 | 118 | #: setup.php:88 119 | msgid "Oauth IMAP" 120 | msgstr "Oauth IMAP" 121 | 122 | #: inc/application.class.php:43 123 | msgid "Oauth IMAP application" 124 | msgid_plural "Oauth IMAP applications" 125 | msgstr[0] "Oauth IMAP application" 126 | msgstr[1] "Oauth IMAP applications" 127 | 128 | #: inc/authorization.class.php:57 129 | msgid "Oauth authorization" 130 | msgid_plural "Oauth authorizations" 131 | msgstr[0] "Oauth authorization" 132 | msgstr[1] "Oauth authorizations" 133 | 134 | #: inc/application.class.php:86 inc/application.class.php:120 135 | msgid "Oauth provider" 136 | msgstr "Oauth provider" 137 | 138 | #: inc/authorization.class.php:309 139 | msgid "Refresh connection diagnostic" 140 | msgstr "Refresh connection diagnostic" 141 | 142 | #: inc/authorization.class.php:280 143 | msgid "SSL" 144 | msgstr "SSL" 145 | 146 | #: inc/authorization.class.php:281 147 | msgid "SSL + TLS" 148 | msgstr "SSL + TLS" 149 | 150 | #: inc/authorization.class.php:273 151 | msgid "Security level" 152 | msgstr "Security level" 153 | 154 | #: inc/authorization.class.php:245 155 | msgid "Server host" 156 | msgstr "Server host" 157 | 158 | #: inc/authorization.class.php:256 159 | msgid "Server port" 160 | msgstr "Server port" 161 | 162 | #: inc/application.class.php:104 inc/application.class.php:137 163 | msgid "Tenant ID" 164 | msgstr "Tenant ID" 165 | 166 | #: inc/authorization.class.php:290 167 | msgid "Timeout" 168 | msgstr "Timeout" 169 | 170 | #: front/authorization.callback.php:76 171 | msgid "Unable to get authorization code" 172 | msgstr "Unable to get authorization code" 173 | 174 | #: front/authorization.callback.php:78 175 | msgid "Unable to save authorization code" 176 | msgstr "Unable to save authorization code" 177 | 178 | #: front/authorization.callback.php:74 179 | msgid "Unable to verify authorization code" 180 | msgstr "Unable to verify authorization code" 181 | 182 | #: inc/authorization.class.php:341 183 | #, php-format 184 | msgid "Unexpected error: %s" 185 | msgstr "Unexpected error: %s" 186 | 187 | #: inc/authorization.class.php:143 188 | msgid "Update" 189 | msgstr "Update" 190 | 191 | #: inc/application.class.php:324 192 | msgid "copy it in the management console of provider" 193 | msgstr "copy it in the management console of provider" 194 | -------------------------------------------------------------------------------- /locales/es_EC.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/locales/es_EC.mo -------------------------------------------------------------------------------- /locales/es_EC.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Soporte Infraestructura Standby, 2023 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2024-09-12 13:10+0000\n" 15 | "PO-Revision-Date: 2020-10-15 13:49+0000\n" 16 | "Last-Translator: Soporte Infraestructura Standby, 2023\n" 17 | "Language-Team: Spanish (Ecuador) (https://app.transifex.com/teclib/teams/28042/es_EC/)\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "Language: es_EC\n" 22 | "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" 23 | 24 | #: front/authorization.callback.php:64 25 | #, php-format 26 | msgid "Authorization failed with error: %s" 27 | msgstr "Autorización fallida con error:%s" 28 | 29 | #: inc/application.class.php:323 30 | msgid "Callback url" 31 | msgstr "URL de devolución de llamada" 32 | 33 | #: inc/application.class.php:92 inc/application.class.php:129 34 | msgid "Client ID" 35 | msgstr "ID de cliente" 36 | 37 | #: inc/application.class.php:98 38 | msgid "Client secret" 39 | msgstr "Secreto de cliente" 40 | 41 | #: inc/authorization.class.php:134 42 | msgid "Connection diagnostic" 43 | msgstr "Diagnóstico de conexión" 44 | 45 | #: inc/mailcollectorfeature.class.php:471 46 | msgid "Connection string" 47 | msgstr "Cadena de conexión" 48 | 49 | #: inc/authorization.class.php:103 50 | msgid "Create an authorization" 51 | msgstr "Crear una autorización" 52 | 53 | #: ajax/dropdownAuthorization.php:49 54 | msgid "Create authorization for another user" 55 | msgstr "Crear autorización para otro usuario" 56 | 57 | #: inc/authorization.class.php:151 58 | msgid "Delete" 59 | msgstr "Borrar" 60 | 61 | #: inc/application.class.php:180 62 | msgid "Developer help for this provider" 63 | msgstr "Ayuda para desarrolladores de este proveedor" 64 | 65 | #: inc/authorization.class.php:139 66 | msgid "Diagnose" 67 | msgstr "Diagnosticar" 68 | 69 | #: inc/authorization.class.php:322 70 | msgid "" 71 | "Diagnostic log contains sensitive information, such as the access token." 72 | msgstr "" 73 | "El registro de diagnóstico contiene información sensible, como el token de " 74 | "acceso." 75 | 76 | #: inc/authorization.class.php:117 inc/authorization.class.php:174 77 | #: inc/authorization.class.php:230 78 | msgid "Email" 79 | msgstr "Correo electrónico" 80 | 81 | #: inc/application.class.php:390 82 | msgid "Invalid provider" 83 | msgstr "Proveedor no válido" 84 | 85 | #: inc/mailcollectorfeature.class.php:473 86 | msgid "Is active ?" 87 | msgstr "Está activo?" 88 | 89 | #: inc/mailcollectorfeature.class.php:472 90 | msgid "Login" 91 | msgstr "Inicio de sesión" 92 | 93 | #: inc/mailcollectorfeature.class.php:402 94 | #, php-format 95 | msgid "Mail receiver \"%s\" has been deactivated." 96 | msgstr "El receptor de correo \"%s\" ha sido desactivado." 97 | 98 | #: inc/mailcollectorfeature.class.php:372 99 | #, php-format 100 | msgid "Mail receiver \"%s\" has been updated." 101 | msgstr "El receptor de correo \"%s\" ha sido actualizado." 102 | 103 | #: inc/mailcollectorfeature.class.php:470 104 | msgid "Name" 105 | msgstr "Nombre" 106 | 107 | #: inc/application.class.php:381 108 | msgid "Name cannot be empty" 109 | msgstr "El nombre no puede estar vacío" 110 | 111 | #: inc/mailcollectorfeature.class.php:467 112 | msgid "No associated receivers." 113 | msgstr "No hay receptores asociados." 114 | 115 | #: inc/authorization.class.php:113 116 | msgid "No authorizations." 117 | msgstr "No hay autorizaciones." 118 | 119 | #: setup.php:88 120 | msgid "Oauth IMAP" 121 | msgstr "Oauth IMAP" 122 | 123 | #: inc/application.class.php:43 124 | msgid "Oauth IMAP application" 125 | msgid_plural "Oauth IMAP applications" 126 | msgstr[0] "Aplicación IMAP Oauth" 127 | msgstr[1] "Aplicaciones IMAP Oauth" 128 | msgstr[2] "Aplicaciones IMAP Oauth" 129 | 130 | #: inc/authorization.class.php:57 131 | msgid "Oauth authorization" 132 | msgid_plural "Oauth authorizations" 133 | msgstr[0] "Autorización Oauth" 134 | msgstr[1] "Autorizaciones Oauth" 135 | msgstr[2] "Autorizaciones Oauth" 136 | 137 | #: inc/application.class.php:86 inc/application.class.php:120 138 | msgid "Oauth provider" 139 | msgstr "Proveedor Oauth" 140 | 141 | #: inc/authorization.class.php:309 142 | msgid "Refresh connection diagnostic" 143 | msgstr "Actualizar diagnóstico de conexión" 144 | 145 | #: inc/authorization.class.php:280 146 | msgid "SSL" 147 | msgstr "SSL" 148 | 149 | #: inc/authorization.class.php:281 150 | msgid "SSL + TLS" 151 | msgstr "SSL + TLS" 152 | 153 | #: inc/authorization.class.php:273 154 | msgid "Security level" 155 | msgstr "Nivel de seguridad" 156 | 157 | #: inc/authorization.class.php:245 158 | msgid "Server host" 159 | msgstr "Servidor" 160 | 161 | #: inc/authorization.class.php:256 162 | msgid "Server port" 163 | msgstr "Puerto del servidor" 164 | 165 | #: inc/application.class.php:104 inc/application.class.php:137 166 | msgid "Tenant ID" 167 | msgstr "ID de inquilino" 168 | 169 | #: inc/authorization.class.php:290 170 | msgid "Timeout" 171 | msgstr "Tiempo de espera" 172 | 173 | #: front/authorization.callback.php:76 174 | msgid "Unable to get authorization code" 175 | msgstr "No se puede obtener el código de autorización" 176 | 177 | #: front/authorization.callback.php:78 178 | msgid "Unable to save authorization code" 179 | msgstr "No se puede guardar el código de autorización" 180 | 181 | #: front/authorization.callback.php:74 182 | msgid "Unable to verify authorization code" 183 | msgstr "No se puede verificar el código de autorización" 184 | 185 | #: inc/authorization.class.php:341 186 | #, php-format 187 | msgid "Unexpected error: %s" 188 | msgstr "Error inesperado:%s" 189 | 190 | #: inc/authorization.class.php:143 191 | msgid "Update" 192 | msgstr "Actualización" 193 | 194 | #: inc/application.class.php:324 195 | msgid "copy it in the management console of provider" 196 | msgstr "cópielo en la consola de gestión del proveedor" 197 | -------------------------------------------------------------------------------- /locales/es_MX.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/locales/es_MX.mo -------------------------------------------------------------------------------- /locales/es_MX.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Carlos Moreno Rodríguez , 2023 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2024-09-12 13:10+0000\n" 15 | "PO-Revision-Date: 2020-10-15 13:49+0000\n" 16 | "Last-Translator: Carlos Moreno Rodríguez , 2023\n" 17 | "Language-Team: Spanish (Mexico) (https://app.transifex.com/teclib/teams/28042/es_MX/)\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "Language: es_MX\n" 22 | "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" 23 | 24 | #: front/authorization.callback.php:64 25 | #, php-format 26 | msgid "Authorization failed with error: %s" 27 | msgstr "La autorización falló con error: %s" 28 | 29 | #: inc/application.class.php:323 30 | msgid "Callback url" 31 | msgstr "URL de redirección" 32 | 33 | #: inc/application.class.php:92 inc/application.class.php:129 34 | msgid "Client ID" 35 | msgstr "Id. de aplicación - cliente" 36 | 37 | #: inc/application.class.php:98 38 | msgid "Client secret" 39 | msgstr "Frase secreta de cliente" 40 | 41 | #: inc/authorization.class.php:134 42 | msgid "Connection diagnostic" 43 | msgstr "Diagnóstico de conexión" 44 | 45 | #: inc/mailcollectorfeature.class.php:471 46 | msgid "Connection string" 47 | msgstr "Cadena de conexión" 48 | 49 | #: inc/authorization.class.php:103 50 | msgid "Create an authorization" 51 | msgstr "Autorizar cuenta" 52 | 53 | #: ajax/dropdownAuthorization.php:49 54 | msgid "Create authorization for another user" 55 | msgstr "Crear autorización para otra cuenta" 56 | 57 | #: inc/authorization.class.php:151 58 | msgid "Delete" 59 | msgstr "Eliminar" 60 | 61 | #: inc/application.class.php:180 62 | msgid "Developer help for this provider" 63 | msgstr "Ayuda para desarrolladores de este proveedor" 64 | 65 | #: inc/authorization.class.php:139 66 | msgid "Diagnose" 67 | msgstr "Diagnosticar" 68 | 69 | #: inc/authorization.class.php:322 70 | msgid "" 71 | "Diagnostic log contains sensitive information, such as the access token." 72 | msgstr "" 73 | "El registro de diagnóstico contiene información confidencial, como el token " 74 | "de acceso." 75 | 76 | #: inc/authorization.class.php:117 inc/authorization.class.php:174 77 | #: inc/authorization.class.php:230 78 | msgid "Email" 79 | msgstr "Dirección de correo electrónico" 80 | 81 | #: inc/application.class.php:390 82 | msgid "Invalid provider" 83 | msgstr "Proveedor no válido" 84 | 85 | #: inc/mailcollectorfeature.class.php:473 86 | msgid "Is active ?" 87 | msgstr "¿Está activo?" 88 | 89 | #: inc/mailcollectorfeature.class.php:472 90 | msgid "Login" 91 | msgstr "Usuario" 92 | 93 | #: inc/mailcollectorfeature.class.php:402 94 | #, php-format 95 | msgid "Mail receiver \"%s\" has been deactivated." 96 | msgstr "Receptor de correo electrónico \\\"%s\\\" se ha desactivado." 97 | 98 | #: inc/mailcollectorfeature.class.php:372 99 | #, php-format 100 | msgid "Mail receiver \"%s\" has been updated." 101 | msgstr "El receptor de correo electrónico \\\"%s\\\" se ha actualizado." 102 | 103 | #: inc/mailcollectorfeature.class.php:470 104 | msgid "Name" 105 | msgstr "Nombre" 106 | 107 | #: inc/application.class.php:381 108 | msgid "Name cannot be empty" 109 | msgstr "El campo nombre no puede estar vacío" 110 | 111 | #: inc/mailcollectorfeature.class.php:467 112 | msgid "No associated receivers." 113 | msgstr "Ningún receptor de correo electrónico asociado." 114 | 115 | #: inc/authorization.class.php:113 116 | msgid "No authorizations." 117 | msgstr "Sin autorizaciones." 118 | 119 | #: setup.php:88 120 | msgid "Oauth IMAP" 121 | msgstr "OAuth IMAP" 122 | 123 | #: inc/application.class.php:43 124 | msgid "Oauth IMAP application" 125 | msgid_plural "Oauth IMAP applications" 126 | msgstr[0] "Aplicación OAuth IMAP" 127 | msgstr[1] "Aplicaciones OAuth IMAP" 128 | msgstr[2] "Aplicaciones OAuth IMAP" 129 | 130 | #: inc/authorization.class.php:57 131 | msgid "Oauth authorization" 132 | msgid_plural "Oauth authorizations" 133 | msgstr[0] "Permiso OAuth" 134 | msgstr[1] "Permisos OAuth" 135 | msgstr[2] "Permisos OAuth" 136 | 137 | #: inc/application.class.php:86 inc/application.class.php:120 138 | msgid "Oauth provider" 139 | msgstr "Proveedor" 140 | 141 | #: inc/authorization.class.php:309 142 | msgid "Refresh connection diagnostic" 143 | msgstr "Actualizar diagnóstico de conexión" 144 | 145 | #: inc/authorization.class.php:280 146 | msgid "SSL" 147 | msgstr "SSL" 148 | 149 | #: inc/authorization.class.php:281 150 | msgid "SSL + TLS" 151 | msgstr "SSL + TLS" 152 | 153 | #: inc/authorization.class.php:273 154 | msgid "Security level" 155 | msgstr "Nivel de seguridad" 156 | 157 | #: inc/authorization.class.php:245 158 | msgid "Server host" 159 | msgstr "Servidor" 160 | 161 | #: inc/authorization.class.php:256 162 | msgid "Server port" 163 | msgstr "Puerto" 164 | 165 | #: inc/application.class.php:104 inc/application.class.php:137 166 | msgid "Tenant ID" 167 | msgstr "Id. de directorio (inquilino)" 168 | 169 | #: inc/authorization.class.php:290 170 | msgid "Timeout" 171 | msgstr "Timeout" 172 | 173 | #: front/authorization.callback.php:76 174 | msgid "Unable to get authorization code" 175 | msgstr "No se puede obtener el código de autorización" 176 | 177 | #: front/authorization.callback.php:78 178 | msgid "Unable to save authorization code" 179 | msgstr "No se puede guardar el código de autorización" 180 | 181 | #: front/authorization.callback.php:74 182 | msgid "Unable to verify authorization code" 183 | msgstr "No se puede verificar el código de autorización" 184 | 185 | #: inc/authorization.class.php:341 186 | #, php-format 187 | msgid "Unexpected error: %s" 188 | msgstr "Error inesperado: %s" 189 | 190 | #: inc/authorization.class.php:143 191 | msgid "Update" 192 | msgstr "Actualizar" 193 | 194 | #: inc/application.class.php:324 195 | msgid "copy it in the management console of provider" 196 | msgstr "copiarlo en la consola de administración del proveedor" 197 | -------------------------------------------------------------------------------- /locales/fr_FR.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/locales/fr_FR.mo -------------------------------------------------------------------------------- /locales/fr_FR.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Cédric Anne, 2021 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2024-09-12 13:10+0000\n" 15 | "PO-Revision-Date: 2020-10-15 13:49+0000\n" 16 | "Last-Translator: Cédric Anne, 2021\n" 17 | "Language-Team: French (France) (https://app.transifex.com/teclib/teams/28042/fr_FR/)\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "Language: fr_FR\n" 22 | "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" 23 | 24 | #: front/authorization.callback.php:64 25 | #, php-format 26 | msgid "Authorization failed with error: %s" 27 | msgstr "L'autorisation a échouée avec l'erreur: %s" 28 | 29 | #: inc/application.class.php:323 30 | msgid "Callback url" 31 | msgstr "Url de retour" 32 | 33 | #: inc/application.class.php:92 inc/application.class.php:129 34 | msgid "Client ID" 35 | msgstr "ID d'application (client)" 36 | 37 | #: inc/application.class.php:98 38 | msgid "Client secret" 39 | msgstr "Secret client" 40 | 41 | #: inc/authorization.class.php:134 42 | msgid "Connection diagnostic" 43 | msgstr "Diagnostic de la connexion" 44 | 45 | #: inc/mailcollectorfeature.class.php:471 46 | msgid "Connection string" 47 | msgstr "Chaîne de connexion" 48 | 49 | #: inc/authorization.class.php:103 50 | msgid "Create an authorization" 51 | msgstr "Créer une autorisation" 52 | 53 | #: ajax/dropdownAuthorization.php:49 54 | msgid "Create authorization for another user" 55 | msgstr "Créer une autorisation pour un autre utilisateur" 56 | 57 | #: inc/authorization.class.php:151 58 | msgid "Delete" 59 | msgstr "Supprimer" 60 | 61 | #: inc/application.class.php:180 62 | msgid "Developer help for this provider" 63 | msgstr "Aide développeur pour ce fournisseur" 64 | 65 | #: inc/authorization.class.php:139 66 | msgid "Diagnose" 67 | msgstr "Diagnostiquer" 68 | 69 | #: inc/authorization.class.php:322 70 | msgid "" 71 | "Diagnostic log contains sensitive information, such as the access token." 72 | msgstr "" 73 | "Le journal de diagnostic contient des informations sensibles, telles que le " 74 | "jeton d'accès." 75 | 76 | #: inc/authorization.class.php:117 inc/authorization.class.php:174 77 | #: inc/authorization.class.php:230 78 | msgid "Email" 79 | msgstr "E-mail" 80 | 81 | #: inc/application.class.php:390 82 | msgid "Invalid provider" 83 | msgstr "Fournisseur invalide" 84 | 85 | #: inc/mailcollectorfeature.class.php:473 86 | msgid "Is active ?" 87 | msgstr "Est actif ?" 88 | 89 | #: inc/mailcollectorfeature.class.php:472 90 | msgid "Login" 91 | msgstr "Identifiant" 92 | 93 | #: inc/mailcollectorfeature.class.php:402 94 | #, php-format 95 | msgid "Mail receiver \"%s\" has been deactivated." 96 | msgstr "Le collecteur mail \"%s\" a été désactivé." 97 | 98 | #: inc/mailcollectorfeature.class.php:372 99 | #, php-format 100 | msgid "Mail receiver \"%s\" has been updated." 101 | msgstr "Le collecteur mail \"%s\" a été mis à jour." 102 | 103 | #: inc/mailcollectorfeature.class.php:470 104 | msgid "Name" 105 | msgstr "Nom" 106 | 107 | #: inc/application.class.php:381 108 | msgid "Name cannot be empty" 109 | msgstr "Le nom ne peut être vide" 110 | 111 | #: inc/mailcollectorfeature.class.php:467 112 | msgid "No associated receivers." 113 | msgstr "Aucun collecteur associé." 114 | 115 | #: inc/authorization.class.php:113 116 | msgid "No authorizations." 117 | msgstr "Aucune autorisation." 118 | 119 | #: setup.php:88 120 | msgid "Oauth IMAP" 121 | msgstr "Oauth IMAP" 122 | 123 | #: inc/application.class.php:43 124 | msgid "Oauth IMAP application" 125 | msgid_plural "Oauth IMAP applications" 126 | msgstr[0] "Application Oauth IMAP" 127 | msgstr[1] "Applications Oauth IMAP" 128 | msgstr[2] "Applications Oauth IMAP" 129 | 130 | #: inc/authorization.class.php:57 131 | msgid "Oauth authorization" 132 | msgid_plural "Oauth authorizations" 133 | msgstr[0] "Autorisation Oauth" 134 | msgstr[1] "Autorisations Oauth" 135 | msgstr[2] "Autorisations Oauth" 136 | 137 | #: inc/application.class.php:86 inc/application.class.php:120 138 | msgid "Oauth provider" 139 | msgstr "Fournisseur Oauth" 140 | 141 | #: inc/authorization.class.php:309 142 | msgid "Refresh connection diagnostic" 143 | msgstr "Actualiser le diagnostic" 144 | 145 | #: inc/authorization.class.php:280 146 | msgid "SSL" 147 | msgstr "SSL" 148 | 149 | #: inc/authorization.class.php:281 150 | msgid "SSL + TLS" 151 | msgstr "SSL + TLS" 152 | 153 | #: inc/authorization.class.php:273 154 | msgid "Security level" 155 | msgstr "Niveau de sécurité" 156 | 157 | #: inc/authorization.class.php:245 158 | msgid "Server host" 159 | msgstr "Hôte du serveur" 160 | 161 | #: inc/authorization.class.php:256 162 | msgid "Server port" 163 | msgstr "Port du serveur" 164 | 165 | #: inc/application.class.php:104 inc/application.class.php:137 166 | msgid "Tenant ID" 167 | msgstr "ID de l'annuaire (locataire)" 168 | 169 | #: inc/authorization.class.php:290 170 | msgid "Timeout" 171 | msgstr "Délai d'attente max." 172 | 173 | #: front/authorization.callback.php:76 174 | msgid "Unable to get authorization code" 175 | msgstr "Impossible d'obtenir le code d'autorisation" 176 | 177 | #: front/authorization.callback.php:78 178 | msgid "Unable to save authorization code" 179 | msgstr "Impossible de sauvegarder le code d'autorisation" 180 | 181 | #: front/authorization.callback.php:74 182 | msgid "Unable to verify authorization code" 183 | msgstr "Impossible de vérifier le code d'autorisation" 184 | 185 | #: inc/authorization.class.php:341 186 | #, php-format 187 | msgid "Unexpected error: %s" 188 | msgstr "Erreur inattendue : %s" 189 | 190 | #: inc/authorization.class.php:143 191 | msgid "Update" 192 | msgstr "Mettre à jour" 193 | 194 | #: inc/application.class.php:324 195 | msgid "copy it in the management console of provider" 196 | msgstr "copiez cette valeur dans la console de gestion du fournisseur" 197 | -------------------------------------------------------------------------------- /locales/oauthimap.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2024-10-02 01:19+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 20 | 21 | #: front/authorization.callback.php:64 22 | #, php-format 23 | msgid "Authorization failed with error: %s" 24 | msgstr "" 25 | 26 | #: inc/application.class.php:323 27 | msgid "Callback url" 28 | msgstr "" 29 | 30 | #: inc/application.class.php:92 inc/application.class.php:129 31 | msgid "Client ID" 32 | msgstr "" 33 | 34 | #: inc/application.class.php:98 35 | msgid "Client secret" 36 | msgstr "" 37 | 38 | #: inc/authorization.class.php:134 39 | msgid "Connection diagnostic" 40 | msgstr "" 41 | 42 | #: inc/mailcollectorfeature.class.php:471 43 | msgid "Connection string" 44 | msgstr "" 45 | 46 | #: inc/authorization.class.php:103 47 | msgid "Create an authorization" 48 | msgstr "" 49 | 50 | #: ajax/dropdownAuthorization.php:49 51 | msgid "Create authorization for another user" 52 | msgstr "" 53 | 54 | #: inc/authorization.class.php:151 55 | msgid "Delete" 56 | msgstr "" 57 | 58 | #: inc/application.class.php:180 59 | msgid "Developer help for this provider" 60 | msgstr "" 61 | 62 | #: inc/authorization.class.php:139 63 | msgid "Diagnose" 64 | msgstr "" 65 | 66 | #: inc/authorization.class.php:322 67 | msgid "" 68 | "Diagnostic log contains sensitive information, such as the access token." 69 | msgstr "" 70 | 71 | #: inc/authorization.class.php:117 inc/authorization.class.php:174 72 | #: inc/authorization.class.php:230 73 | msgid "Email" 74 | msgstr "" 75 | 76 | #: inc/application.class.php:390 77 | msgid "Invalid provider" 78 | msgstr "" 79 | 80 | #: inc/mailcollectorfeature.class.php:473 81 | msgid "Is active ?" 82 | msgstr "" 83 | 84 | #: inc/mailcollectorfeature.class.php:472 85 | msgid "Login" 86 | msgstr "" 87 | 88 | #: inc/mailcollectorfeature.class.php:402 89 | #, php-format 90 | msgid "Mail receiver \"%s\" has been deactivated." 91 | msgstr "" 92 | 93 | #: inc/mailcollectorfeature.class.php:372 94 | #, php-format 95 | msgid "Mail receiver \"%s\" has been updated." 96 | msgstr "" 97 | 98 | #: inc/mailcollectorfeature.class.php:470 99 | msgid "Name" 100 | msgstr "" 101 | 102 | #: inc/application.class.php:381 103 | msgid "Name cannot be empty" 104 | msgstr "" 105 | 106 | #: inc/mailcollectorfeature.class.php:467 107 | msgid "No associated receivers." 108 | msgstr "" 109 | 110 | #: inc/authorization.class.php:113 111 | msgid "No authorizations." 112 | msgstr "" 113 | 114 | #: setup.php:88 115 | msgid "Oauth IMAP" 116 | msgstr "" 117 | 118 | #: inc/application.class.php:43 119 | msgid "Oauth IMAP application" 120 | msgid_plural "Oauth IMAP applications" 121 | msgstr[0] "" 122 | msgstr[1] "" 123 | 124 | #: inc/authorization.class.php:57 125 | msgid "Oauth authorization" 126 | msgid_plural "Oauth authorizations" 127 | msgstr[0] "" 128 | msgstr[1] "" 129 | 130 | #: inc/application.class.php:86 inc/application.class.php:120 131 | msgid "Oauth provider" 132 | msgstr "" 133 | 134 | #: inc/authorization.class.php:309 135 | msgid "Refresh connection diagnostic" 136 | msgstr "" 137 | 138 | #: inc/authorization.class.php:280 139 | msgid "SSL" 140 | msgstr "" 141 | 142 | #: inc/authorization.class.php:281 143 | msgid "SSL + TLS" 144 | msgstr "" 145 | 146 | #: inc/authorization.class.php:273 147 | msgid "Security level" 148 | msgstr "" 149 | 150 | #: inc/authorization.class.php:245 151 | msgid "Server host" 152 | msgstr "" 153 | 154 | #: inc/authorization.class.php:256 155 | msgid "Server port" 156 | msgstr "" 157 | 158 | #: inc/application.class.php:104 inc/application.class.php:137 159 | msgid "Tenant ID" 160 | msgstr "" 161 | 162 | #: inc/authorization.class.php:290 163 | msgid "Timeout" 164 | msgstr "" 165 | 166 | #: front/authorization.callback.php:76 167 | msgid "Unable to get authorization code" 168 | msgstr "" 169 | 170 | #: front/authorization.callback.php:78 171 | msgid "Unable to save authorization code" 172 | msgstr "" 173 | 174 | #: front/authorization.callback.php:74 175 | msgid "Unable to verify authorization code" 176 | msgstr "" 177 | 178 | #: inc/authorization.class.php:341 179 | #, php-format 180 | msgid "Unexpected error: %s" 181 | msgstr "" 182 | 183 | #: inc/authorization.class.php:143 184 | msgid "Update" 185 | msgstr "" 186 | 187 | #: inc/application.class.php:324 188 | msgid "copy it in the management console of provider" 189 | msgstr "" 190 | -------------------------------------------------------------------------------- /locales/pt_BR.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/locales/pt_BR.mo -------------------------------------------------------------------------------- /locales/pt_BR.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Jean Vergaças , 2020 8 | # Cédric Anne, 2021 9 | # Matheus Rafael, 2023 10 | # Diego Nobre , 2023 11 | # Eduardo Mozart de Oliveira , 2024 12 | # 13 | #, fuzzy 14 | msgid "" 15 | msgstr "" 16 | "Project-Id-Version: PACKAGE VERSION\n" 17 | "Report-Msgid-Bugs-To: \n" 18 | "POT-Creation-Date: 2024-10-02 01:19+0000\n" 19 | "PO-Revision-Date: 2020-10-15 13:49+0000\n" 20 | "Last-Translator: Eduardo Mozart de Oliveira , 2024\n" 21 | "Language-Team: Portuguese (Brazil) (https://app.transifex.com/teclib/teams/28042/pt_BR/)\n" 22 | "MIME-Version: 1.0\n" 23 | "Content-Type: text/plain; charset=UTF-8\n" 24 | "Content-Transfer-Encoding: 8bit\n" 25 | "Language: pt_BR\n" 26 | "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" 27 | 28 | #: front/authorization.callback.php:64 29 | #, php-format 30 | msgid "Authorization failed with error: %s" 31 | msgstr "Falha na autorização com erro: %s" 32 | 33 | #: inc/application.class.php:323 34 | msgid "Callback url" 35 | msgstr "URL de callback" 36 | 37 | #: inc/application.class.php:92 inc/application.class.php:129 38 | msgid "Client ID" 39 | msgstr "ID do Cliente" 40 | 41 | #: inc/application.class.php:98 42 | msgid "Client secret" 43 | msgstr "Segredo do cliente" 44 | 45 | #: inc/authorization.class.php:134 46 | msgid "Connection diagnostic" 47 | msgstr "Diagnóstico de conexão" 48 | 49 | #: inc/mailcollectorfeature.class.php:471 50 | msgid "Connection string" 51 | msgstr "String de conexão" 52 | 53 | #: inc/authorization.class.php:103 54 | msgid "Create an authorization" 55 | msgstr "Crie uma autorização" 56 | 57 | #: ajax/dropdownAuthorization.php:49 58 | msgid "Create authorization for another user" 59 | msgstr "Criar autorização para outro usuário" 60 | 61 | #: inc/authorization.class.php:151 62 | msgid "Delete" 63 | msgstr "Excluir" 64 | 65 | #: inc/application.class.php:180 66 | msgid "Developer help for this provider" 67 | msgstr "Ajuda do desenvolvedor para este provedor" 68 | 69 | #: inc/authorization.class.php:139 70 | msgid "Diagnose" 71 | msgstr "Diagnosticar" 72 | 73 | #: inc/authorization.class.php:322 74 | msgid "" 75 | "Diagnostic log contains sensitive information, such as the access token." 76 | msgstr "" 77 | "O log de diagnóstico contém informações confidenciais, como o token de " 78 | "acesso." 79 | 80 | #: inc/authorization.class.php:117 inc/authorization.class.php:174 81 | #: inc/authorization.class.php:230 82 | msgid "Email" 83 | msgstr "E-mail" 84 | 85 | #: inc/application.class.php:390 86 | msgid "Invalid provider" 87 | msgstr "Provedor inválido" 88 | 89 | #: inc/mailcollectorfeature.class.php:473 90 | msgid "Is active ?" 91 | msgstr "Está ativo?" 92 | 93 | #: inc/mailcollectorfeature.class.php:472 94 | msgid "Login" 95 | msgstr "Login" 96 | 97 | #: inc/mailcollectorfeature.class.php:402 98 | #, php-format 99 | msgid "Mail receiver \"%s\" has been deactivated." 100 | msgstr "O receptor de e-mail \"%s\" foi desativado." 101 | 102 | #: inc/mailcollectorfeature.class.php:372 103 | #, php-format 104 | msgid "Mail receiver \"%s\" has been updated." 105 | msgstr "O receptor de e-mail \"%s\" foi atualizado." 106 | 107 | #: inc/mailcollectorfeature.class.php:470 108 | msgid "Name" 109 | msgstr "Nome" 110 | 111 | #: inc/application.class.php:381 112 | msgid "Name cannot be empty" 113 | msgstr "O nome não pode ser vazio" 114 | 115 | #: inc/mailcollectorfeature.class.php:467 116 | msgid "No associated receivers." 117 | msgstr "Sem receptores associados." 118 | 119 | #: inc/authorization.class.php:113 120 | msgid "No authorizations." 121 | msgstr "Sem autorizações." 122 | 123 | #: setup.php:88 124 | msgid "Oauth IMAP" 125 | msgstr "OAuth IMAP" 126 | 127 | #: inc/application.class.php:43 128 | msgid "Oauth IMAP application" 129 | msgid_plural "Oauth IMAP applications" 130 | msgstr[0] "Aplicação OAuth IMAP" 131 | msgstr[1] "Aplicações OAuth IMAP" 132 | msgstr[2] "Aplicações OAuth IMAP" 133 | 134 | #: inc/authorization.class.php:57 135 | msgid "Oauth authorization" 136 | msgid_plural "Oauth authorizations" 137 | msgstr[0] "Autorização Oauth" 138 | msgstr[1] "Autorizações Oauth" 139 | msgstr[2] "Autorizações OAuth" 140 | 141 | #: inc/application.class.php:86 inc/application.class.php:120 142 | msgid "Oauth provider" 143 | msgstr "Provedor OAuth" 144 | 145 | #: inc/authorization.class.php:309 146 | msgid "Refresh connection diagnostic" 147 | msgstr "Atualizar diagnóstico de conexão" 148 | 149 | #: inc/authorization.class.php:280 150 | msgid "SSL" 151 | msgstr "SSL" 152 | 153 | #: inc/authorization.class.php:281 154 | msgid "SSL + TLS" 155 | msgstr "SSL + TLS" 156 | 157 | #: inc/authorization.class.php:273 158 | msgid "Security level" 159 | msgstr "Nível de segurança" 160 | 161 | #: inc/authorization.class.php:245 162 | msgid "Server host" 163 | msgstr "Host do servidor" 164 | 165 | #: inc/authorization.class.php:256 166 | msgid "Server port" 167 | msgstr "Porta do servidor" 168 | 169 | #: inc/application.class.php:104 inc/application.class.php:137 170 | msgid "Tenant ID" 171 | msgstr "ID do Cliente" 172 | 173 | #: inc/authorization.class.php:290 174 | msgid "Timeout" 175 | msgstr "Timeout" 176 | 177 | #: front/authorization.callback.php:76 178 | msgid "Unable to get authorization code" 179 | msgstr "Incapaz de obter código de autorização" 180 | 181 | #: front/authorization.callback.php:78 182 | msgid "Unable to save authorization code" 183 | msgstr "Incapaz de salvar código de autorização" 184 | 185 | #: front/authorization.callback.php:74 186 | msgid "Unable to verify authorization code" 187 | msgstr "Não é possível verificar o código de autorização" 188 | 189 | #: inc/authorization.class.php:341 190 | #, php-format 191 | msgid "Unexpected error: %s" 192 | msgstr "Erro inesperado: %s" 193 | 194 | #: inc/authorization.class.php:143 195 | msgid "Update" 196 | msgstr "Atualizar" 197 | 198 | #: inc/application.class.php:324 199 | msgid "copy it in the management console of provider" 200 | msgstr "copiá-lo no console de gerenciamento do provedor" 201 | -------------------------------------------------------------------------------- /locales/sk_SK.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginsGLPI/oauthimap/2a65ae137588d6f8fe51efb23aae4583f8fa3910/locales/sk_SK.mo -------------------------------------------------------------------------------- /locales/sk_SK.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # feonsu , 2023 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2024-09-12 13:10+0000\n" 15 | "PO-Revision-Date: 2020-10-15 13:49+0000\n" 16 | "Last-Translator: feonsu , 2023\n" 17 | "Language-Team: Slovak (Slovakia) (https://app.transifex.com/teclib/teams/28042/sk_SK/)\n" 18 | "MIME-Version: 1.0\n" 19 | "Content-Type: text/plain; charset=UTF-8\n" 20 | "Content-Transfer-Encoding: 8bit\n" 21 | "Language: sk_SK\n" 22 | "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" 23 | 24 | #: front/authorization.callback.php:64 25 | #, php-format 26 | msgid "Authorization failed with error: %s" 27 | msgstr "Autorizácia zlyhala s chybou: %s" 28 | 29 | #: inc/application.class.php:323 30 | msgid "Callback url" 31 | msgstr "URL adresa spätného volania" 32 | 33 | #: inc/application.class.php:92 inc/application.class.php:129 34 | msgid "Client ID" 35 | msgstr "ID klienta" 36 | 37 | #: inc/application.class.php:98 38 | msgid "Client secret" 39 | msgstr "Tajný kľúč klienta" 40 | 41 | #: inc/authorization.class.php:134 42 | msgid "Connection diagnostic" 43 | msgstr "Diagnostika pripojenia" 44 | 45 | #: inc/mailcollectorfeature.class.php:471 46 | msgid "Connection string" 47 | msgstr "Reťazec pripojenia" 48 | 49 | #: inc/authorization.class.php:103 50 | msgid "Create an authorization" 51 | msgstr "Vytvoriť autorizáciu" 52 | 53 | #: ajax/dropdownAuthorization.php:49 54 | msgid "Create authorization for another user" 55 | msgstr "Vytvoriť autorizáciu pre iného používateľa" 56 | 57 | #: inc/authorization.class.php:151 58 | msgid "Delete" 59 | msgstr "Odstrániť" 60 | 61 | #: inc/application.class.php:180 62 | msgid "Developer help for this provider" 63 | msgstr "Nápoveda vývojára pre tohto poskytovateľa" 64 | 65 | #: inc/authorization.class.php:139 66 | msgid "Diagnose" 67 | msgstr "Diagnostika" 68 | 69 | #: inc/authorization.class.php:322 70 | msgid "" 71 | "Diagnostic log contains sensitive information, such as the access token." 72 | msgstr "" 73 | "Diagnostický log obsahuje citlivé informácie, ako napríklad prístupový " 74 | "token." 75 | 76 | #: inc/authorization.class.php:117 inc/authorization.class.php:174 77 | #: inc/authorization.class.php:230 78 | msgid "Email" 79 | msgstr "E-mail" 80 | 81 | #: inc/application.class.php:390 82 | msgid "Invalid provider" 83 | msgstr "Neplatný poskytovateľ" 84 | 85 | #: inc/mailcollectorfeature.class.php:473 86 | msgid "Is active ?" 87 | msgstr "Je aktívny?" 88 | 89 | #: inc/mailcollectorfeature.class.php:472 90 | msgid "Login" 91 | msgstr "Prihlásenie" 92 | 93 | #: inc/mailcollectorfeature.class.php:402 94 | #, php-format 95 | msgid "Mail receiver \"%s\" has been deactivated." 96 | msgstr "E-mailový prijímač \"%s\" bol deaktivovaný." 97 | 98 | #: inc/mailcollectorfeature.class.php:372 99 | #, php-format 100 | msgid "Mail receiver \"%s\" has been updated." 101 | msgstr "E-mailový prijímač \"%s\" bol aktualizovaný." 102 | 103 | #: inc/mailcollectorfeature.class.php:470 104 | msgid "Name" 105 | msgstr "Názov" 106 | 107 | #: inc/application.class.php:381 108 | msgid "Name cannot be empty" 109 | msgstr "Názov nemôže byť prázdny" 110 | 111 | #: inc/mailcollectorfeature.class.php:467 112 | msgid "No associated receivers." 113 | msgstr "Žiadne priradené prijímače." 114 | 115 | #: inc/authorization.class.php:113 116 | msgid "No authorizations." 117 | msgstr "Žiadne autorizácie." 118 | 119 | #: setup.php:88 120 | msgid "Oauth IMAP" 121 | msgstr "Oauth IMAP" 122 | 123 | #: inc/application.class.php:43 124 | msgid "Oauth IMAP application" 125 | msgid_plural "Oauth IMAP applications" 126 | msgstr[0] "Oauth IMAP aplikácia" 127 | msgstr[1] "Oauth IMAP aplikácie" 128 | msgstr[2] "Oauth IMAP aplikácie" 129 | msgstr[3] "Oauth IMAP aplikácie" 130 | 131 | #: inc/authorization.class.php:57 132 | msgid "Oauth authorization" 133 | msgid_plural "Oauth authorizations" 134 | msgstr[0] "Oauth autorizácia" 135 | msgstr[1] "Oauth autorizácie" 136 | msgstr[2] "Oauth autorizácie" 137 | msgstr[3] "Oauth autorizácie" 138 | 139 | #: inc/application.class.php:86 inc/application.class.php:120 140 | msgid "Oauth provider" 141 | msgstr "Oauth poskytovateľ" 142 | 143 | #: inc/authorization.class.php:309 144 | msgid "Refresh connection diagnostic" 145 | msgstr "Obnoviť diagnostiku pripojenia" 146 | 147 | #: inc/authorization.class.php:280 148 | msgid "SSL" 149 | msgstr "SSL" 150 | 151 | #: inc/authorization.class.php:281 152 | msgid "SSL + TLS" 153 | msgstr "SSL + TLS" 154 | 155 | #: inc/authorization.class.php:273 156 | msgid "Security level" 157 | msgstr "Úroveň zabezpečenia" 158 | 159 | #: inc/authorization.class.php:245 160 | msgid "Server host" 161 | msgstr "Názov servera" 162 | 163 | #: inc/authorization.class.php:256 164 | msgid "Server port" 165 | msgstr "Port servera" 166 | 167 | #: inc/application.class.php:104 inc/application.class.php:137 168 | msgid "Tenant ID" 169 | msgstr "ID tenantu" 170 | 171 | #: inc/authorization.class.php:290 172 | msgid "Timeout" 173 | msgstr "Časový limit" 174 | 175 | #: front/authorization.callback.php:76 176 | msgid "Unable to get authorization code" 177 | msgstr "Nepodarilo sa získať autorizačný kód" 178 | 179 | #: front/authorization.callback.php:78 180 | msgid "Unable to save authorization code" 181 | msgstr "Nepodarilo sa uložiť autorizačný kód" 182 | 183 | #: front/authorization.callback.php:74 184 | msgid "Unable to verify authorization code" 185 | msgstr "Nepodarilo sa overiť autorizačný kód" 186 | 187 | #: inc/authorization.class.php:341 188 | #, php-format 189 | msgid "Unexpected error: %s" 190 | msgstr "Neočakávaná chyba: %s" 191 | 192 | #: inc/authorization.class.php:143 193 | msgid "Update" 194 | msgstr "Aktualizovať" 195 | 196 | #: inc/application.class.php:324 197 | msgid "copy it in the management console of provider" 198 | msgstr "skopírujte ju do konzoly na správu poskytovateľa" 199 | -------------------------------------------------------------------------------- /oauthimap.xml: -------------------------------------------------------------------------------- 1 | 2 | Oauth IMAP 3 | oauthimap 4 | stable 5 | https://raw.githubusercontent.com/pluginsGLPI/oauthimap/main/docs/logo.png 6 | 7 | 8 | Ce plugin supporte la connexion Oauth pour les collecteurs de mails 9 | This plugin supports Oauth connection for emails receivers 10 | 11 | 12 | 13 | Ce plugin supporte la connexion Oauth pour les collecteurs de mails. 14 | Ceci permet de collecter des emails sur les boîtes mail des domaines G Suite et Azure AD. 15 | 16 | 17 | This plugin supports Oauth connection for emails receivers 18 | It permits emails fetching from G Suite and Azure AD mailboxes. 19 | 20 | 21 | 22 | https://github.com/pluginsGLPI/oauthimap/ 23 | https://github.com/pluginsGLPI/oauthimap/releases 24 | https://github.com/pluginsGLPI/oauthimap/issues 25 | https://glpi-plugins.readthedocs.io/en/latest/oauthimap/index.html 26 | 27 | TECLIB' 28 | 29 | 30 | 31 | 1.4.3 32 | ~10.0.7 33 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.4.3/glpi-oauthimap-1.4.3.tar.bz2 34 | 35 | 36 | 1.4.2 37 | ~10.0.0 38 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.4.2/glpi-oauthimap-1.4.2.tar.bz2 39 | 40 | 41 | 1.4.1 42 | ~10.0.0 43 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.4.1/glpi-oauthimap-1.4.1.tar.bz2 44 | 45 | 46 | 1.4.0 47 | ~10.0.0 48 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.4.0/glpi-oauthimap-1.4.0.tar.bz2 49 | 50 | 51 | 1.3.4 52 | ~9.5.0 53 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.3.4/glpi-oauthimap-1.3.4.tar.bz2 54 | 55 | 56 | 1.3.3 57 | ~9.5.0 58 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.3.3/glpi-oauthimap-1.3.3.tar.bz2 59 | 60 | 61 | 1.3.2 62 | ~9.5.0 63 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.3.2/glpi-oauthimap-1.3.2.tar.bz2 64 | 65 | 66 | 1.3.1 67 | ~9.5.0 68 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.3.1/glpi-oauthimap-1.3.1.tar.bz2 69 | 70 | 71 | 1.3.0 72 | ~9.5.0 73 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.3.0/glpi-oauthimap-1.3.0.tar.bz2 74 | 75 | 76 | 1.2.0 77 | ~9.5.0 78 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.2.0/glpi-oauthimap-1.2.0.tar.bz2 79 | 80 | 81 | 1.1.0 82 | ~9.5.0 83 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.1.0/glpi-oauthimap-1.1.0.tar.bz2 84 | 85 | 86 | 1.0.4 87 | ~9.5.0 88 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.0.4/glpi-oauthimap-1.0.4.tar.bz2 89 | 90 | 91 | 1.0.3 92 | ~9.5.0 93 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.0.3/glpi-oauthimap-1.0.3.tar.bz2 94 | 95 | 96 | 1.0.2 97 | ~9.5.0 98 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.0.2/glpi-oauthimap-1.0.2.tar.bz2 99 | 100 | 101 | 1.0.1 102 | ~9.5.0 103 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.0.1/glpi-oauthimap-1.0.1.tar.bz2 104 | 105 | 106 | 1.0.0 107 | ~9.5.0 108 | https://github.com/pluginsGLPI/oauthimap/releases/download/1.0.0/glpi-oauthimap-1.0.0.tar.bz2 109 | 110 | 111 | 112 | en_GB 113 | fr_FR 114 | 115 | GPL v2+ 116 | 117 | 118 | IMAP 119 | oauth 120 | collecteur 121 | 122 | 123 | IMAP 124 | oauth 125 | receiver 126 | 127 | 128 | 129 | https://raw.githubusercontent.com/pluginsGLPI/oauthimap/main/docs/screenshots/config.png 130 | https://raw.githubusercontent.com/pluginsGLPI/oauthimap/main/docs/screenshots/config_oauth_mailcollector.png 131 | 132 | 133 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | parallel: 3 | maximumNumberOfProcesses: 2 4 | level: 5 5 | bootstrapFiles: 6 | - ../../inc/based_config.php 7 | paths: 8 | - inc 9 | - front 10 | - ajax 11 | - hook.php 12 | - setup.php 13 | scanDirectories: 14 | - ../../inc 15 | - ../../src 16 | stubFiles: 17 | - ../../stubs/glpi_constants.php 18 | rules: 19 | - GlpiProject\Tools\PHPStan\Rules\GlobalVarTypeRule 20 | -------------------------------------------------------------------------------- /setup.php: -------------------------------------------------------------------------------- 1 | . 24 | * ------------------------------------------------------------------------- 25 | * @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 26 | * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 27 | * @link https://github.com/pluginsGLPI/oauthimap 28 | * ------------------------------------------------------------------------- 29 | */ 30 | 31 | define('PLUGIN_OAUTHIMAP_VERSION', '1.4.3'); 32 | 33 | // Minimal GLPI version, inclusive 34 | define('PLUGIN_OAUTHIMAP_MIN_GLPI', '10.0.11'); 35 | // Maximum GLPI version, exclusive 36 | define('PLUGIN_OAUTHIMAP_MAX_GLPI', '10.0.99'); 37 | 38 | define('PLUGIN_OAUTHIMAP_ROOT', Plugin::getPhpDir('oauthimap')); 39 | 40 | use GlpiPlugin\Oauthimap\MailCollectorFeature; 41 | 42 | function plugin_init_oauthimap() 43 | { 44 | /** @var array $PLUGIN_HOOKS */ 45 | global $PLUGIN_HOOKS; 46 | 47 | $PLUGIN_HOOKS['csrf_compliant']['oauthimap'] = true; 48 | 49 | if (Plugin::isPluginActive('oauthimap')) { 50 | // Config page: redirect to dropdown page 51 | $PLUGIN_HOOKS['config_page']['oauthimap'] = 'front/config.form.php'; 52 | 53 | // Menu link 54 | $PLUGIN_HOOKS['menu_toadd']['oauthimap'] = [ 55 | 'config' => 'PluginOauthimapApplication', 56 | ]; 57 | 58 | // Secured fields that are encrypted 59 | $PLUGIN_HOOKS['secured_fields']['oauthimap'] = [ 60 | PluginOauthimapApplication::getTableField('client_secret'), 61 | PluginOauthimapAuthorization::getTableField('code'), 62 | PluginOauthimapAuthorization::getTableField('token'), 63 | PluginOauthimapAuthorization::getTableField('refresh_token'), 64 | ]; 65 | 66 | // Plugin hooks 67 | $PLUGIN_HOOKS['post_item_form']['oauthimap'] = [PluginOauthimapHook::class, 'postItemForm']; 68 | 69 | // MailCollector hooks 70 | $PLUGIN_HOOKS['mail_server_protocols']['oauthimap'] = function (array $additionnal_protocols) { 71 | return array_merge($additionnal_protocols, MailCollectorFeature::getMailProtocols()); 72 | }; 73 | $PLUGIN_HOOKS['pre_item_update']['oauthimap'] = [ 74 | 'MailCollector' => [MailCollectorFeature::class, 'forceMailCollectorUpdate'], 75 | ]; 76 | $PLUGIN_HOOKS['item_add']['oauthimap'] = [ 77 | 'MailCollector' => [MailCollectorFeature::class, 'handleMailCollectorSaving'], 78 | ]; 79 | $PLUGIN_HOOKS['item_update']['oauthimap'] = [ 80 | 'MailCollector' => [MailCollectorFeature::class, 'handleMailCollectorSaving'], 81 | ]; 82 | } 83 | } 84 | 85 | function plugin_version_oauthimap() 86 | { 87 | return [ 88 | 'name' => __('Oauth IMAP', 'oauthimap'), 89 | 'version' => PLUGIN_OAUTHIMAP_VERSION, 90 | 'author' => 'Teclib\'', 91 | 'license' => 'GPL v2+', 92 | 'homepage' => 'http://www.teclib.com', 93 | 'requirements' => [ 94 | 'glpi' => [ 95 | 'min' => PLUGIN_OAUTHIMAP_MIN_GLPI, 96 | 'max' => PLUGIN_OAUTHIMAP_MAX_GLPI, 97 | ], 98 | 'php' => [ 99 | 'exts' => [ 100 | 'openssl' => [ 101 | 'required' => true, 102 | 'function' => 'openssl_x509_read', 103 | ], 104 | ], 105 | ], 106 | ], 107 | ]; 108 | } 109 | -------------------------------------------------------------------------------- /tools/HEADER: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------- 2 | OauthIMAP plugin for GLPI 3 | ------------------------------------------------------------------------- 4 | 5 | LICENSE 6 | 7 | This file is part of OauthIMAP. 8 | 9 | OauthIMAP is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | 14 | OauthIMAP is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with OauthIMAP. If not, see . 21 | ------------------------------------------------------------------------- 22 | @copyright Copyright (C) 2020-2022 by OauthIMAP plugin team. 23 | @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html 24 | @link https://github.com/pluginsGLPI/oauthimap 25 | ------------------------------------------------------------------------- 26 | --------------------------------------------------------------------------------