├── .editorconfig
├── .github
├── CODEOWNERS
├── FUNDING.yml
├── boring-cyborg.yml
├── dependabot.yml
├── settings.yml
└── workflows
│ ├── ci.yml
│ ├── craft-release.yaml
│ ├── label-sponsors.yml
│ └── set-milestone-on-pr.yaml
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── composer-require-checker.json
├── composer.json
├── composer.lock
├── etc
└── qa
│ ├── .phpunit.result.cache
│ ├── composer-require-checker.json
│ ├── phpcs.xml
│ ├── phpstan.neon
│ ├── phpunit.xml
│ └── psalm.xml
├── infection.json.dist
├── src
├── Definition
│ ├── AccessToken.php
│ ├── ConsumerKey.php
│ ├── ConsumerSecret.php
│ ├── RequestToken.php
│ └── TokenSecret.php
├── RequestSigning
│ └── RequestSigner.php
└── Signature
│ ├── HmacMd5Signature.php
│ ├── HmacSha1Signature.php
│ ├── HmacSha256Signature.php
│ ├── HmacSha384Signature.php
│ ├── HmacSha512Signature.php
│ ├── HmacSignature.php
│ └── Signature.php
└── var
└── .gitkeep
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.json]
11 | indent_size = 2
12 |
13 | [Makefile]
14 | indent_style = tab
15 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @WyriHaximus
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: WyriHaximus
2 |
--------------------------------------------------------------------------------
/.github/boring-cyborg.yml:
--------------------------------------------------------------------------------
1 | labelPRBasedOnFilePath:
2 | "Documentation 📚":
3 | - README.md
4 | - CONTRIBUTING.md
5 | "Dependencies 📦":
6 | - Dockerfile*
7 | - composer.*
8 | - package.json
9 | - package-lock.json
10 | - yarn.lock
11 | "Docker 🐳":
12 | - Dockerfile*
13 | - .docker/**/*
14 | "Image 🖼":
15 | - "**/*.gif"
16 | - "**/*.jpg"
17 | - "**/*.jpeg"
18 | - "**/*.png"
19 | - "**/*.webp"
20 | "CSS 👩🎨":
21 | - "**/*.css"
22 | "HTML 👷♀️":
23 | - "**/*.htm"
24 | - "**/*.html"
25 | "NEON 🦹♂️":
26 | - "**/*.neon"
27 | "MarkDown 📝":
28 | - "**/*.md"
29 | "YAML 🍄":
30 | - "**/*.yml"
31 | - "**/*.yaml"
32 | "JSON 👨💼":
33 | - "**/*.json"
34 | "Go 🐹":
35 | - "**/*.go"
36 | "JavaScript 🦏":
37 | - "**/*.js"
38 | - package.json
39 | - package-lock.json
40 | - yarn.lock
41 | "PHP 🐘":
42 | - "**/*.php"
43 | - composer.*
44 | "Configuration ⚙":
45 | - .github/*
46 | "CI 🚧":
47 | - .github/workflows/*
48 | - .scrutinizer.yml
49 | "Templates 🌲":
50 | - "**/*.twig"
51 | - "**/*.tpl"
52 | "Helm ☸":
53 | - .helm/**/*
54 | "Tests 🧪":
55 | - tests/**/*
56 | "Source 🔮":
57 | - src/**/*
58 |
59 | labelerFlags:
60 | labelOnPRUpdates: true
61 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "composer"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | labels:
8 | - "Dependencies 📦"
9 | - "PHP 🐘"
10 | versioning-strategy: "widen"
11 | open-pull-requests-limit: 1
12 |
--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | repository:
2 | private: false
3 | has_issues: true
4 | has_wiki: false
5 | has_downloads: true
6 | default_branch: master
7 | allow_squash_merge: false
8 | allow_merge_commit: true
9 | allow_rebase_merge: false
10 |
11 | # Labels: define labels for Issues and Pull Requests
12 | labels:
13 | - name: "Dependencies 📦"
14 | color: 0025ff
15 | description: "Pull requests that update a dependency file"
16 | - name: "Image 🖼"
17 | color: 00ffff
18 | - name: "HTML 👷♀️"
19 | color: ffffff
20 | - name: "CSS 👩🎨"
21 | color: b3b3b3
22 | - name: "JavaScript 🦏"
23 | color: ffff00
24 | - name: "Go 🐹"
25 | color: 00ADD8
26 | - name: "JSON 👨💼"
27 | color: 00ADD8
28 | - name: "NEON 🦹♂️"
29 | color: CE3262
30 | - name: "MarkDown 📝"
31 | color: 000000
32 | - name: "YAML 🍄"
33 | color: ff1aff
34 | - name: "Templates 🌲"
35 | color: 009933
36 | - name: "Helm ☸"
37 | color: 091C84
38 | - name: "Tests 🧪"
39 | color: ffe6e6
40 | - name: "Source 🔮"
41 | color: e6ffe6
42 | - name: "Configuration ⚙"
43 | color: b3b3cc
44 | - name: "PHP 🐘"
45 | color: 8892BF
46 | description: "Hypertext Pre Processor"
47 | - name: "Docker 🐳"
48 | color: 0db7ed
49 | description: "Pull requests that relate to Docker"
50 | - name: "CI 🚧"
51 | color: ffff00
52 | - name: "Feature 🏗"
53 | color: 66ff99
54 | - name: "Documentation 📚"
55 | color: 6666ff
56 | - name: "Security 🕵️♀️"
57 | color: ff0000
58 | - name: "Hacktoberfest 🎃"
59 | color: 152347
60 | - name: "Bug 🐞"
61 | color: d73a4a
62 | description: "Something isn't working"
63 | oldname: bug
64 | - name: "Duplicate ♊"
65 | color: cfd3d7
66 | description: "This issue or pull request already exists"
67 | oldname: duplicate
68 | - name: "Enhancement ✨"
69 | color: a2eeef
70 | description: "New feature or request"
71 | oldname: enhancement
72 | - name: "Good First Issue"
73 | color: 7057ff
74 | description: "Good for newcomers"
75 | oldname: "good first issue"
76 | - name: "Help Wanted"
77 | color: 008672
78 | description: "Extra attention is needed"
79 | oldname: "help wanted"
80 | - name: Invalid
81 | color: e4e669
82 | description: "This doesn't seem right"
83 | oldname: invalid
84 | - name: "Question ❓"
85 | color: d876e3
86 | description: "Further information is requested"
87 | oldname: question
88 | - name: "Will not be fixed 🛑"
89 | color: ffffff
90 | description: "This will not be worked on"
91 | oldname: wontfix
92 | - name: "Sponsor Request ❤️"
93 | color: fedbf0
94 | description: "Issue/PR opened by sponsor"
95 |
96 | branches:
97 | - name: master
98 | protection:
99 | required_pull_request_reviews:
100 | required_approving_review_count: 1
101 | dismiss_stale_reviews: true
102 | require_code_owner_reviews: true
103 | # Required. Require status checks to pass before merging. Set to null to disable
104 | required_status_checks:
105 | # Required. Require branches to be up to date before merging.
106 | strict: true
107 | # Required. The list of status checks to require in order to merge into this branch
108 | contexts: []
109 | # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable.
110 | enforce_admins: true
111 | # Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable.
112 | restrictions:
113 | apps: []
114 | users: []
115 | teams: []
116 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration
2 | on:
3 | push:
4 | branches:
5 | - 'main'
6 | - 'master'
7 | - 'refs/heads/v[0-9]+.[0-9]+.[0-9]+'
8 | pull_request:
9 | jobs:
10 | supported-versions-matrix:
11 | name: Supported Versions Matrix
12 | runs-on: ubuntu-latest
13 | needs:
14 | - lint-yaml
15 | - lint-json
16 | outputs:
17 | version: ${{ steps.supported-versions-matrix.outputs.version }}
18 | steps:
19 | - uses: actions/checkout@v2
20 | - id: supported-versions-matrix
21 | uses: WyriHaximus/github-action-composer-php-versions-in-range@v1
22 | supported-checks-matrix:
23 | name: Supported Checks Matrix
24 | runs-on: ubuntu-latest
25 | needs:
26 | - lint-yaml
27 | - composer-install
28 | outputs:
29 | check: ${{ steps.supported-checks-matrix.outputs.check }}
30 | steps:
31 | - uses: actions/checkout@v2
32 | - id: supported-checks-matrix
33 | name: Generate check
34 | run: |
35 | printf "Checks found: %s\r\n" $(make task-list-ci)
36 | printf "::set-output name=check::%s" $(make task-list-ci)
37 | composer-install:
38 | strategy:
39 | fail-fast: false
40 | matrix:
41 | php: ${{ fromJson(needs.supported-versions-matrix.outputs.version) }}
42 | composer: [lowest, current, highest]
43 | needs:
44 | - lint-yaml
45 | - lint-json
46 | - supported-versions-matrix
47 | runs-on: ubuntu-latest
48 | container:
49 | image: ghcr.io/wyrihaximusnet/php:${{ matrix.php }}-nts-buster-dev-root
50 | steps:
51 | - uses: actions/checkout@v2
52 | - name: Cache composer packages
53 | uses: actions/cache@v1
54 | with:
55 | path: ./vendor/
56 | key: ${{ matrix.composer }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
57 | - name: Install Dependencies
58 | run: composer update --prefer-lowest --no-progress --ansi --no-interaction --prefer-dist -o
59 | if: matrix.composer == 'lowest'
60 | - name: Install Dependencies
61 | run: composer install --ansi --no-progress --no-interaction --prefer-dist -o
62 | if: matrix.composer == 'current'
63 | - name: Install Dependencies
64 | run: composer update --ansi --no-progress --no-interaction --prefer-dist -o
65 | if: matrix.composer == 'highest'
66 | qa:
67 | name: Run ${{ matrix.check }} on PHP ${{ matrix.php }} with ${{ matrix.composer }} dependency preference (Linux)
68 | strategy:
69 | fail-fast: false
70 | matrix:
71 | php: ${{ fromJson(needs.supported-versions-matrix.outputs.version) }}
72 | composer: [lowest, current, highest]
73 | check: ${{ fromJson(needs.supported-checks-matrix.outputs.check) }}
74 | needs:
75 | - lint-yaml
76 | - lint-json
77 | - composer-install
78 | - supported-checks-matrix
79 | - supported-versions-matrix
80 | runs-on: ubuntu-latest
81 | container:
82 | image: ghcr.io/wyrihaximusnet/php:${{ matrix.php }}-nts-buster-dev-root
83 | steps:
84 | - uses: actions/checkout@v2
85 | - name: Cache composer packages
86 | uses: actions/cache@v1
87 | with:
88 | path: ./vendor/
89 | key: ${{ matrix.composer }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
90 | - name: Install Dependencies
91 | run: (test -f vendor && true ) || composer update --prefer-lowest --no-progress --ansi --no-interaction --prefer-dist -o
92 | if: matrix.composer == 'lowest'
93 | - name: Install Dependencies
94 | run: (test -f vendor && true ) || composer install --ansi --no-progress --no-interaction --prefer-dist -o
95 | if: matrix.composer == 'current'
96 | - name: Install Dependencies
97 | run: (test -f vendor && true ) || composer update --ansi --no-progress --no-interaction --prefer-dist -o
98 | if: matrix.composer == 'highest'
99 | - name: Fetch Tags
100 | run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* || true
101 | if: matrix.check == 'backward-compatibility-check'
102 | - run: make ${{ matrix.check }}
103 | env:
104 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
105 | COVERALLS_RUN_LOCALLY: ${{ secrets.COVERALLS_RUN_LOCALLY }}
106 | unittests-directly-on-os:
107 | name: Run unit tests on PHP ${{ matrix.php }} with ${{ matrix.composer }} dependency preference (${{ matrix.os }})
108 | strategy:
109 | fail-fast: false
110 | matrix:
111 | os: [ubuntu-latest, windows-latest, macos-latest]
112 | php: ${{ fromJson(needs.supported-versions-matrix.outputs.version) }}
113 | composer: [lowest, current, highest]
114 | needs:
115 | - lint-yaml
116 | - lint-json
117 | - composer-install
118 | - supported-versions-matrix
119 | runs-on: ${{ matrix.os }}
120 | steps:
121 | - uses: actions/checkout@v2
122 | - name: Setup PHP, extensions and composer with shivammathur/setup-php
123 | uses: shivammathur/setup-php@v2
124 | with:
125 | php-version: ${{ matrix.php }}
126 | coverage: xdebug, pcov
127 | extensions: intl, sodium
128 | - name: Cache composer packages
129 | uses: actions/cache@v1
130 | with:
131 | path: ./vendor/
132 | key: ${{ matrix.composer }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
133 | - name: Install Dependencies
134 | run: (test -f vendor && true ) || composer update --prefer-lowest --no-progress --ansi --no-interaction --prefer-dist -o
135 | if: matrix.composer == 'lowest'
136 | - name: Install Dependencies
137 | run: (test -f vendor && true ) || composer install --ansi --no-progress --no-interaction --prefer-dist -o
138 | if: matrix.composer == 'current'
139 | - name: Install Dependencies
140 | run: (test -f vendor && true ) || composer update --ansi --no-progress --no-interaction --prefer-dist -o
141 | if: matrix.composer == 'highest'
142 | - name: Fetch Tags
143 | run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* || true
144 | if: matrix.check == 'backward-compatibility-check'
145 | - run: |
146 | ./vendor/bin/phpunit -c ./etc/qa/phpunit.xml
147 | env:
148 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
149 | COVERALLS_RUN_LOCALLY: ${{ secrets.COVERALLS_RUN_LOCALLY }}
150 | lint-yaml:
151 | name: Lint YAML
152 | runs-on: ubuntu-latest
153 | steps:
154 | - uses: actions/checkout@v2
155 | - name: yaml-lint
156 | uses: ibiqlik/action-yamllint@v3
157 | with:
158 | config_data: |
159 | extends: default
160 | ignore: |
161 | /.git/
162 | rules:
163 | line-length: disable
164 | document-start: disable
165 | truthy: disable
166 | lint-json:
167 | name: Lint JSON
168 | runs-on: ubuntu-latest
169 | steps:
170 | - uses: actions/checkout@v2
171 | - name: json-syntax-check
172 | uses: limitusus/json-syntax-check@v1
173 | with:
174 | pattern: "\\.json$"
175 | check-mark:
176 | name: ✔️
177 | needs:
178 | - lint-yaml
179 | - lint-json
180 | - qa
181 | - unittests-directly-on-os
182 | runs-on: ubuntu-latest
183 | steps:
184 | - run: echo "✔️"
185 |
--------------------------------------------------------------------------------
/.github/workflows/craft-release.yaml:
--------------------------------------------------------------------------------
1 | name: Create Release
2 | env:
3 | MILESTONE: ${{ github.event.milestone.title }}
4 | on:
5 | milestone:
6 | types:
7 | - closed
8 | jobs:
9 | wait-for-status-checks:
10 | name: Wait for status checks
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - run: sleep 13
15 | - name: 'Wait for status checks'
16 | id: waitforstatuschecks
17 | uses: "WyriHaximus/github-action-wait-for-status@master"
18 | with:
19 | ignoreActions: "Wait for status checks"
20 | checkInterval: 5
21 | env:
22 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
23 | - id: generate-version-strategy
24 | if: steps.waitforstatuschecks.outputs.status != 'success'
25 | name: Fail
26 | run: exit 1
27 | generate-changelog:
28 | name: Generate Changelog
29 | needs:
30 | - wait-for-status-checks
31 | runs-on: ubuntu-latest
32 | outputs:
33 | changelog: ${{ steps.changelog.outputs.changelog }}
34 | steps:
35 | - name: Generate changelog
36 | uses: WyriHaximus/github-action-jwage-changelog-generator@v1
37 | id: changelog
38 | env:
39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 | with:
41 | milestone: ${{ env.MILESTONE }}
42 | - name: Show changelog
43 | run: echo "${CHANGELOG}"
44 | env:
45 | CHANGELOG: ${{ steps.changelog.outputs.changelog }}
46 | create-release:
47 | name: Create Release
48 | needs:
49 | - generate-changelog
50 | runs-on: ubuntu-latest
51 | steps:
52 | - uses: actions/checkout@v2
53 | env:
54 | CHANGELOG: ${{ needs.generate-changelog.outputs.changelog }}
55 | - run: |
56 | echo -e "${MILESTONE_DESCRIPTION}\r\n\r\n${CHANGELOG}" > release-${{ env.MILESTONE }}-release-message.md
57 | cat release-${{ env.MILESTONE }}-release-message.md
58 | release_message=$(cat release-${{ env.MILESTONE }}-release-message.md)
59 | release_message="${release_message//'%'/'%25'}"
60 | release_message="${release_message//$'\n'/'%0A'}"
61 | release_message="${release_message//$'\r'/'%0D'}"
62 | echo "::set-output name=release_message::$release_message"
63 | id: releasemessage
64 | env:
65 | MILESTONE_DESCRIPTION: ${{ github.event.milestone.description }}
66 | CHANGELOG: ${{ needs.generate-changelog.outputs.changelog }}
67 | - name: Create Reference Release with Changelog
68 | uses: fleskesvor/create-release@feature/support-target-commitish
69 | env:
70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71 | with:
72 | tag_name: ${{ env.MILESTONE }}
73 | release_name: ${{ env.MILESTONE }}
74 | body: ${{ steps.releasemessage.outputs.release_message }}
75 | draft: false
76 | prerelease: false
77 |
--------------------------------------------------------------------------------
/.github/workflows/label-sponsors.yml:
--------------------------------------------------------------------------------
1 | name: Label sponsors ❤️
2 | on:
3 | pull_request:
4 | types:
5 | - opened
6 | issues:
7 | types:
8 | - opened
9 | jobs:
10 | sponsor-label:
11 | name: Label sponsors ❤️
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: JasonEtco/is-sponsor-label-action@v1
15 | with:
16 | label: Sponsor Request ❤️
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/set-milestone-on-pr.yaml:
--------------------------------------------------------------------------------
1 | name: Set Milestone
2 | on:
3 | pull_request:
4 | types:
5 | - assigned
6 | - opened
7 | - synchronize
8 | - reopened
9 | - edited
10 | - ready_for_review
11 | - review_requested
12 | jobs:
13 | set-milestone:
14 | name: Set Milestone
15 | if: github.event.pull_request.milestone == null
16 | runs-on: ubuntu-latest
17 | outputs:
18 | check: ${{ steps.generate-checks-strategy.outputs.check }}
19 | steps:
20 | - uses: actions/checkout@v2
21 | - name: 'Get Previous tag'
22 | id: previoustag
23 | uses: "WyriHaximus/github-action-get-previous-tag@v1"
24 | env:
25 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
26 | - name: 'Get next versions'
27 | id: semvers
28 | uses: "WyriHaximus/github-action-next-semvers@v1"
29 | with:
30 | version: ${{ steps.previoustag.outputs.tag }}
31 | - name: 'Decide which version fits this PR'
32 | id: decidedversion
33 | run: |
34 | if [ "$(jq '.sender.id' -r ${GITHUB_EVENT_PATH})" = "49699333" ]; then
35 | printf "::set-output name=version::%s" "${PATCH}"
36 | exit 0
37 | fi
38 |
39 | composer install --no-progress --ansi --no-interaction --prefer-dist -o -q
40 |
41 | if ! (./vendor/bin/roave-backward-compatibility-check); then
42 | printf "::set-output name=version::%s" "${MAJOR}"
43 | exit 0
44 | fi
45 |
46 | printf "::set-output name=version::%s" "${MINOR}"
47 | env:
48 | MAJOR: ${{ steps.semvers.outputs.major }}
49 | MINOR: ${{ steps.semvers.outputs.minor }}
50 | PATCH: ${{ steps.semvers.outputs.patch }}
51 | - name: 'Get Milestones'
52 | uses: "WyriHaximus/github-action-get-milestones@master"
53 | id: milestones
54 | env:
55 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
56 | - run: printf "::set-output name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number')
57 | id: querymilestone
58 | env:
59 | MILESTONES: ${{ steps.milestones.outputs.milestones }}
60 | MILESTONE: ${{ steps.decidedversion.outputs.version }}
61 | - name: 'Create Milestone'
62 | if: steps.querymilestone.outputs.number == ''
63 | id: createmilestone
64 | uses: "WyriHaximus/github-action-create-milestone@0.1.0"
65 | with:
66 | title: ${{ steps.decidedversion.outputs.version }}
67 | env:
68 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
69 | - name: 'Select found or created Milestone'
70 | id: selectmilestone
71 | run: |
72 | if [ $(echo ${QUERY_NUMBER} | wc -c) -eq 1 ] ; then
73 | printf "::set-output name=number::%s" "${CREATED_NUMBER}"
74 | exit 0
75 | fi
76 |
77 | printf "::set-output name=number::%s" "${QUERY_NUMBER}"
78 | env:
79 | CREATED_NUMBER: ${{ steps.createmilestone.outputs.number }}
80 | QUERY_NUMBER: ${{ steps.querymilestone.outputs.number }}
81 | - name: 'Set Milestone'
82 | uses: "WyriHaximus/github-action-set-milestone@master"
83 | with:
84 | issue_number: ${{ github.event.pull_request.number }}
85 | milestone_number: ${{ steps.selectmilestone.outputs.number }}
86 | env:
87 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
88 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Pull requests are highly appreciated. Here's a quick guide.
4 |
5 | Fork, then clone the repo:
6 |
7 | git clone git@github.com:your-username/psr7-oauth1.git
8 |
9 | Set up your machine:
10 |
11 | composer install
12 |
13 | Make sure the tests pass:
14 |
15 | make unit
16 |
17 | Make your change. Add tests for your change. Make the tests pass:
18 |
19 | make unit
20 |
21 | Before committing and submitting your pull request make sure it passes PSR2 coding style, unit tests pass and pass on all supported PHP versions:
22 |
23 | make contrib
24 |
25 | Push to your fork and [submit a pull request][pr].
26 |
27 | [pr]: https://help.github.com/articles/creating-a-pull-request/
28 |
29 | At this point you're waiting on me. I like to at least comment on pull requests
30 | within a day or two. I may suggest some changes or improvements or alternatives.
31 |
32 | Some things that will increase the chance that your pull request is accepted:
33 |
34 | * Write tests.
35 | * Follow PSR2 (travis will also check for this).
36 | * Write a [good commit message][commit].
37 |
38 | [commit]: http://chris.beams.io/posts/git-commit/
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Cees-Jan Kiewiet & Beau Simensen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # set all to phony
2 | SHELL=bash
3 |
4 | .PHONY: *
5 |
6 | COMPOSER_CACHE_DIR=$(shell composer config --global cache-dir -q || echo ${HOME}/.composer-php/cache)
7 | COMPOSER_SHOW_EXTENSION_LIST=$(shell composer show -t | grep -o "\-\-\(ext-\).\+" | sort | uniq | cut -d- -f4- | tr -d '\n' | grep . | sed '/^$$/d' | xargs | sed -e 's/ /, /g' | tr -cd '[:alnum:],' | sed 's/.$$//')
8 | SLIM_DOCKER_IMAGE=$(shell php -r 'echo count(array_intersect(["gd", "vips"], explode(",", "${COMPOSER_SHOW_EXTENSION_LIST}"))) > 0 ? "" : "-slim";')
9 |
10 | ifneq ("$(wildcard /.you-are-in-a-wyrihaximus.net-php-docker-image)","")
11 | IN_DOCKER=TRUE
12 | else
13 | IN_DOCKER=FALSE
14 | endif
15 |
16 | ifeq ("$(IN_DOCKER)","TRUE")
17 | DOCKER_RUN:=
18 | else
19 | PHP_VERSION:=$(shell docker run --rm -v "`pwd`:`pwd`" jess/jq jq -r -c '.config.platform.php' "`pwd`/composer.json" | php -r "echo str_replace('|', '.', explode('.', implode('|', explode('.', stream_get_contents(STDIN), 2)), 2)[0]);")
20 | DOCKER_RUN:=docker run --rm -it \
21 | -v "`pwd`:`pwd`" \
22 | -v "${COMPOSER_CACHE_DIR}:/home/app/.composer/cache" \
23 | -w "`pwd`" \
24 | "ghcr.io/wyrihaximusnet/php:${PHP_VERSION}-nts-alpine${SLIM_DOCKER_IMAGE}-dev"
25 | endif
26 |
27 | ifneq (,$(findstring icrosoft,$(shell cat /proc/version)))
28 | THREADS=1
29 | else
30 | THREADS=$(shell nproc)
31 | endif
32 |
33 | all: ## Runs everything ###
34 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "###" | awk 'BEGIN {FS = ":.*?## "}; {printf "%s\n", $$1}' | xargs --open-tty $(MAKE)
35 |
36 | syntax-php: ## Lint PHP syntax
37 | $(DOCKER_RUN) vendor/bin/parallel-lint --exclude vendor .
38 |
39 | cs-fix: ## Fix any automatically fixable code style issues
40 | $(DOCKER_RUN) vendor/bin/phpcbf --parallel=$(THREADS) --cache=./var/.phpcs.cache.json --standard=./etc/qa/phpcs.xml || $(DOCKER_RUN) vendor/bin/phpcbf --parallel=$(THREADS) --cache=./var/.phpcs.cache.json --standard=./etc/qa/phpcs.xml || $(DOCKER_RUN) vendor/bin/phpcbf --parallel=$(THREADS) --cache=./var/.phpcs.cache.json --standard=./etc/qa/phpcs.xml -vvvv
41 |
42 | cs: ## Check the code for code style issues
43 | $(DOCKER_RUN) vendor/bin/phpcs --parallel=$(THREADS) --cache=./var/.phpcs.cache.json --standard=./etc/qa/phpcs.xml
44 |
45 | stan: ## Run static analysis (PHPStan)
46 | $(DOCKER_RUN) vendor/bin/phpstan analyse src tests --level max --ansi -c ./etc/qa/phpstan.neon
47 |
48 | psalm: ## Run static analysis (Psalm)
49 | $(DOCKER_RUN) vendor/bin/psalm --threads=$(THREADS) --shepherd --stats --config=./etc/qa/psalm.xml
50 |
51 | unit-testing: ## Run tests
52 | $(DOCKER_RUN) vendor/bin/phpunit --colors=always -c ./etc/qa/phpunit.xml --coverage-text --coverage-html ./var/tests-unit-coverage-html --coverage-clover ./var/tests-unit-clover-coverage.xml
53 | $(DOCKER_RUN) test -n "$(COVERALLS_REPO_TOKEN)" && test -n "$(COVERALLS_RUN_LOCALLY)" && test -f ./var/tests-unit-clover-coverage.xml && vendor/bin/php-coveralls -v --coverage_clover ./build/logs/clover.xml --json_path ./var/tests-unit-clover-coverage-upload.json || true
54 |
55 | mutation-testing: ## Run mutation testing
56 | $(DOCKER_RUN) vendor/bin/infection --ansi --min-msi=100 --min-covered-msi=100 --threads=$(THREADS) --ignore-msi-with-no-mutations || (cat ./var/infection.log && false)
57 |
58 | composer-require-checker: ## Ensure we require every package used in this package directly
59 | $(DOCKER_RUN) vendor/bin/composer-require-checker --ignore-parse-errors --ansi -vvv --config-file=./etc/qa/composer-require-checker.json
60 |
61 | composer-unused: ## Ensure we don't require any package we don't use in this package directly
62 | $(DOCKER_RUN) composer unused --ansi
63 |
64 | composer-install: ## Install dependencies
65 | $(DOCKER_RUN) composer install --no-progress --ansi --no-interaction --prefer-dist -o
66 |
67 | backward-compatibility-check: ## Check code for backwards incompatible changes
68 | $(MAKE) backward-compatibility-check-raw || true
69 |
70 | backward-compatibility-check-raw: ## Check code for backwards incompatible changes, doesn't ignore the failure ###
71 | $(DOCKER_RUN) vendor/bin/roave-backward-compatibility-check
72 |
73 | shell: ## Provides Shell access in the expected environment ###
74 | $(DOCKER_RUN) ash
75 |
76 | task-list-ci: ## CI: Generate a JSON array of jobs to run, matches the commands run when running `make (|all)` ###
77 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "###" | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%s\n", $$1}' | jq --raw-input --slurp -c 'split("\n")| .[0:-1]'
78 |
79 | help: ## Show this help ###
80 | @printf "\033[33mUsage:\033[0m\n make [target]\n\n\033[33mTargets:\033[0m\n"
81 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[32m%-32s\033[0m %s\n", $$1, $$2}' | tr -d '#'
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Client-side PSR-7 Oauth1 request signer for PHP `^8 || ^7.4`
2 |
3 | [](https://github.com/php-api-clients/psr7-oauth1/actions/workflows/ci.yml)
4 | [](https://packagist.org/packages/api-clients/psr7-oauth1)
5 | [](https://packagist.org/packages/api-clients/psr7-oauth1/stats)
6 | [](https://scrutinizer-ci.com/g/php-api-clients/psr7-oauth1/?branch=master)
7 | [](https://packagist.org/packages/api-clients/psr7-oauth1)
8 |
9 | # Installation
10 |
11 | To install via [Composer](http://getcomposer.org/), use the command below, it will automatically detect the latest version and bind it with `^`.
12 |
13 | ```bash
14 | composer require api-clients/psr7-oauth1
15 | ```
16 |
17 | In case you need to support `5.5+` as well in your package, we suggest you use the following command:
18 |
19 | ```bash
20 | composer require api-clients/psr7-oauth1:^1.0
21 | ```
22 |
23 | However, since `1.0`, `2.0`, and `3.0` of this package are 100% compatible, we recommend you use the following command to support both:
24 |
25 | ```bash
26 | composer require "api-clients/psr7-oauth1:^2.0 || ^1.0"
27 | ```
28 |
29 | # Example
30 |
31 | ```php
32 | withAccessToken(
43 | new Definition\AccessToken('token_key'),
44 | new Definition\TokenSecret('token_secret')
45 | )->sign($request);
46 | ```
47 |
48 | # Suppported signatures
49 |
50 | All supported signatures are HMAC signatures.
51 |
52 | ## MD5
53 |
54 | Signs request with `HMAC-MD5`. Usage:
55 |
56 | ```php
57 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ../../src
10 | ../../tests
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/etc/qa/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | ergebnis:
3 | classesAllowedToBeExtended:
4 | - Exception
5 | - ApiClients\Tools\Psr7\Oauth1\Signature\HmacSignature
6 | - ApiClients\Tools\Psr7\Oauth1\Signature\Signature
7 | - ApiClients\Tests\Tools\Psr7\Oauth1\Signature\AbstractHmacSignatureTest
8 |
9 | includes:
10 | - ../../vendor/wyrihaximus/async-test-utilities/rules.neon
11 |
--------------------------------------------------------------------------------
/etc/qa/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ../../tests/
6 |
7 |
8 |
9 |
10 | ../../src/
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/etc/qa/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/infection.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "timeout": 120,
3 | "source": {
4 | "directories": [
5 | "src"
6 | ]
7 | },
8 | "logs": {
9 | "text": "./var/infection.log",
10 | "summary": "./var/infection-summary.log",
11 | "json": "./var/infection.json",
12 | "perMutator": "./var/infection-per-mutator.md"
13 | },
14 | "mutators": {
15 | "@default": true,
16 | "InstanceOf_": {
17 | "ignore": [
18 | "ApiClients\\Tools\\Psr7\\Oauth1\\RequestSigning\\RequestSigner::sign",
19 | "ApiClients\\Tools\\Psr7\\Oauth1\\Signature\\Signature::getKey"
20 | ]
21 | },
22 | "UnwrapStrRepeat": false,
23 | "UnwrapStrShuffle": false
24 | },
25 | "phpUnit": {
26 | "configDir": "./etc/qa/"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Definition/AccessToken.php:
--------------------------------------------------------------------------------
1 | accessToken;
19 | }
20 |
21 | /**
22 | * @deprecated Use accessToken property
23 | */
24 | public function getToken(): string
25 | {
26 | return $this->accessToken;
27 | }
28 |
29 | public function __toString(): string
30 | {
31 | return $this->accessToken;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Definition/ConsumerKey.php:
--------------------------------------------------------------------------------
1 | consumerKey;
19 | }
20 |
21 | public function __toString(): string
22 | {
23 | return $this->consumerKey;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Definition/ConsumerSecret.php:
--------------------------------------------------------------------------------
1 | consumerSecret;
19 | }
20 |
21 | public function __toString(): string
22 | {
23 | return $this->consumerSecret;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Definition/RequestToken.php:
--------------------------------------------------------------------------------
1 | requestToken;
19 | }
20 |
21 | /**
22 | * @deprecated Use requestToken property
23 | */
24 | public function getToken(): string
25 | {
26 | return $this->requestToken;
27 | }
28 |
29 | public function __toString(): string
30 | {
31 | return $this->requestToken;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Definition/TokenSecret.php:
--------------------------------------------------------------------------------
1 | tokenSecret;
19 | }
20 |
21 | public function __toString(): string
22 | {
23 | return $this->tokenSecret;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/RequestSigning/RequestSigner.php:
--------------------------------------------------------------------------------
1 | signature = $signature ?? new HmacSha1Signature($consumerSecret);
40 | }
41 |
42 | public function withAccessToken(AccessToken $accessToken, TokenSecret $tokenSecret): RequestSigner
43 | {
44 | $clone = clone $this;
45 | $clone->accessToken = $accessToken;
46 | $clone->signature = $clone->signature->withTokenSecret($tokenSecret);
47 |
48 | return $clone;
49 | }
50 |
51 | public function withoutAccessToken(): RequestSigner
52 | {
53 | $clone = clone $this;
54 | $clone->accessToken = null;
55 | $clone->signature = $clone->signature->withoutTokenSecret();
56 |
57 | return $clone;
58 | }
59 |
60 | public function sign(RequestInterface $request): RequestInterface
61 | {
62 | $parameters = [
63 | 'oauth_consumer_key' => (string) $this->consumerKey,
64 | 'oauth_nonce' => $this->generateNonce(),
65 | 'oauth_signature_method' => $this->signature->getMethod(),
66 | 'oauth_timestamp' => $this->generateTimestamp(),
67 | 'oauth_version' => '1.0',
68 | ];
69 |
70 | if ($this->accessToken instanceof AccessToken) {
71 | $parameters['oauth_token'] = (string) $this->accessToken;
72 | }
73 |
74 | $parameters = $this->mergeSignatureParameter($request, $parameters);
75 |
76 | return $request->withHeader('Authorization', $this->generateAuthorizationHeader($parameters));
77 | }
78 |
79 | /**
80 | * @param array $additionalParameters
81 | */
82 | public function signToRequestAuthorization(
83 | RequestInterface $request,
84 | string $callbackUri,
85 | array $additionalParameters = []
86 | ): RequestInterface {
87 | $parameters = [
88 | 'oauth_consumer_key' => (string) $this->consumerKey,
89 | 'oauth_nonce' => $this->generateNonce(),
90 | 'oauth_signature_method' => $this->signature->getMethod(),
91 | 'oauth_timestamp' => $this->generateTimestamp(),
92 | 'oauth_version' => '1.0',
93 | 'oauth_callback' => $callbackUri,
94 | ];
95 |
96 | $parameters = array_merge($parameters, $additionalParameters);
97 |
98 | $parameters = $this->mergeSignatureParameter($request, $parameters);
99 |
100 | return $request->withHeader('Authorization', $this->generateAuthorizationHeader($parameters));
101 | }
102 |
103 | /**
104 | * @param array $parameters
105 | *
106 | * @return array
107 | */
108 | private function mergeSignatureParameter(RequestInterface $request, array $parameters): array
109 | {
110 | $body = [];
111 | if (
112 | $request->getMethod() === 'POST' &&
113 | $request->getHeaderLine('Content-Type') === 'application/x-www-form-urlencoded'
114 | ) {
115 | parse_str((string) $request->getBody(), $body);
116 | }
117 |
118 | $signature = $this->signature->sign($request->getUri(), array_merge($body, $parameters), $request->getMethod());
119 |
120 | $parameters['oauth_signature'] = $signature;
121 |
122 | return $parameters;
123 | }
124 |
125 | private function generateTimestamp(): string
126 | {
127 | $dateTime = new DateTimeImmutable();
128 |
129 | return $dateTime->format('U');
130 | }
131 |
132 | private function generateNonce(int $length = self::DEFAULT_NONCE_LENGTH): string
133 | {
134 | $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
135 |
136 | return substr(str_shuffle(str_repeat($pool, self::NONCE_REPLICATION)), self::START, $length);
137 | }
138 |
139 | /**
140 | * @param array $parameters
141 | */
142 | private function generateAuthorizationHeader(array $parameters): string
143 | {
144 | $header = 'OAuth ';
145 | foreach ($parameters as $key => $value) {
146 | $header .= rawurlencode($key) . '="' . rawurlencode($value) . '",';
147 | }
148 |
149 | return rtrim($header, ',');
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Signature/HmacMd5Signature.php:
--------------------------------------------------------------------------------
1 | getHashingAlgorithm()));
27 | }
28 |
29 | /**
30 | * @param array $parameters
31 | */
32 | final public function sign(UriInterface $uri, array $parameters = [], string $method = 'POST'): string
33 | {
34 | $baseString = $this->generateBaseString($uri, $parameters, $method);
35 |
36 | return base64_encode($this->hash($baseString));
37 | }
38 |
39 | /**
40 | * @param array $parameters
41 | */
42 | private function generateBaseString(UriInterface $uri, array $parameters = [], string $method = 'POST'): string
43 | {
44 | $baseString = [rawurlencode($method)];
45 |
46 | $scheme = $uri->getScheme();
47 | $host = $uri->getHost();
48 | $path = $uri->getPath();
49 | $port = $uri->getPort();
50 |
51 | $baseString[] = rawurlencode($port === null
52 | ? sprintf('%s://%s%s', $scheme, $host, $path)
53 | : sprintf('%s://%s:%d%s', $scheme, $host, $port, $path));
54 |
55 | $data = [];
56 |
57 | parse_str($uri->getQuery(), $query);
58 |
59 | foreach (array_merge($query, $parameters) as $key => $value) {
60 | $data[rawurlencode($key)] = rawurlencode($value);
61 | }
62 |
63 | ksort($data);
64 |
65 | array_walk($data, static function (string &$value, string $key): void {
66 | $value = $key . '=' . $value;
67 | });
68 |
69 | $baseString[] = rawurlencode(implode('&', $data));
70 |
71 | return implode('&', $baseString);
72 | }
73 |
74 | private function hash(string $string): string
75 | {
76 | return hash_hmac($this->getHashingAlgorithm(), $string, $this->getKey(), true);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Signature/Signature.php:
--------------------------------------------------------------------------------
1 | tokenSecret = $tokenSecret;
25 |
26 | return $clone;
27 | }
28 |
29 | final public function withoutTokenSecret(): Signature
30 | {
31 | $clone = clone $this;
32 | $clone->tokenSecret = null;
33 |
34 | return $clone;
35 | }
36 |
37 | final protected function getKey(): string
38 | {
39 | $key = rawurlencode((string) $this->consumerSecret) . '&';
40 |
41 | if ($this->tokenSecret instanceof TokenSecret) {
42 | $key .= rawurlencode((string) $this->tokenSecret);
43 | }
44 |
45 | return $key;
46 | }
47 |
48 | abstract public function getMethod(): string;
49 |
50 | /**
51 | * @param array $parameters
52 | */
53 | abstract public function sign(UriInterface $uri, array $parameters = [], string $method = 'POST'): string;
54 | }
55 |
--------------------------------------------------------------------------------
/var/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php-api-clients/psr7-oauth1/dcbe6547fc7cd5b37e0195d42ebd54a219a13828/var/.gitkeep
--------------------------------------------------------------------------------