├── .coveralls.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── Vagrantfile
├── application-passwords.css
├── application-passwords.js
├── application-passwords.php
├── assets
├── icon-128x128.png
├── icon-256x256.png
├── icon.svg
├── screenshot-1.png
└── screenshot-2.png
├── auth-app.js
├── class.application-passwords-list-table.php
├── class.application-passwords.php
├── composer.json
├── composer.lock
├── docker-compose.yml
├── gruntfile.js
├── package-lock.json
├── package.json
├── phpcs.xml.dist
├── phpunit.xml.dist
├── readme.md
├── readme.txt
└── tests
├── logs
└── .gitignore
└── test-class.application-passwords.php
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 | coverage_clover: build/logs/clover.xml
3 | json_path: build/logs/coveralls-upload.json
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | indent_style = tab
9 |
10 | [*.{json,yml,yaml}]
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /node_modules/
3 | **/*.min.js
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | "extends": [
6 | "plugin:@wordpress/eslint-plugin/es5"
7 | ],
8 | "rules": {
9 | "camelcase": "off",
10 | "vars-on-top": "warn",
11 | "comma-dangle": "off"
12 | },
13 | "globals": {
14 | "jQuery": "readonly"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vagrant/
2 | /vendor/
3 | /node_modules/
4 | /dist/
5 | /phpcs.xml
6 | /phpunit.xml
7 | /console.log
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: xenial
2 |
3 | language: php
4 |
5 | php:
6 | - "5.6"
7 | - "7.0"
8 | - "7.2"
9 | - "7.3"
10 | - "7.4"
11 |
12 | services:
13 | - mysql
14 |
15 | addons:
16 | apt:
17 | packages:
18 | - libxml2-utils # For xmllint.
19 |
20 | env:
21 | - WP_VERSION=latest WP_MULTISITE=0
22 | - WP_VERSION=latest WP_MULTISITE=1
23 | - WP_VERSION=trunk WP_MULTISITE=0
24 | - WP_VERSION=trunk WP_MULTISITE=1
25 |
26 | install:
27 | - composer self-update --1
28 | - npm install
29 | - export DEV_LIB_PATH="vendor/xwp/wp-dev-lib/scripts"
30 | - source "$DEV_LIB_PATH/travis.install.sh"
31 |
32 | script:
33 | - npm run lint-js
34 | - source "$DEV_LIB_PATH/travis.script.sh"
35 |
36 | after_script:
37 | - source "$DEV_LIB_PATH/travis.after_script.sh"
38 |
39 | cache:
40 | directories:
41 | - $HOME/.composer/cache
42 | - $HOME/.npm
43 |
44 | branches:
45 | only:
46 | - master
47 |
48 | notifications:
49 | email: false
50 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | load File.join(
2 | File.dirname(__FILE__),
3 | "vendor/wpsh/local/Vagrantfile"
4 | )
5 |
6 | Vagrant.configure(2) do |config|
7 | config.vm.hostname = "application-passwords"
8 | end
9 |
--------------------------------------------------------------------------------
/application-passwords.css:
--------------------------------------------------------------------------------
1 | .app-pass-dialog-background {
2 | opacity: 1.0;
3 | background: rgba(0,0,0,0.7);
4 | }
5 |
6 | .app-pass-dialog {
7 | padding: 20px;
8 | }
9 |
10 | .new-application-password-content {
11 | padding-bottom: 20px;
12 | padding-top: 10px;
13 | }
--------------------------------------------------------------------------------
/application-passwords.js:
--------------------------------------------------------------------------------
1 | /* global appPass, wp */
2 | /* eslint-disable no-alert */
3 | ( function( $, appPass ) {
4 | var $appPassSection = $( '#application-passwords-section' ),
5 | $newAppPassForm = $appPassSection.find( '.create-application-password' ),
6 | $newAppPassField = $newAppPassForm.find( '.input' ),
7 | $newAppPassButton = $newAppPassForm.find( '.button' ),
8 | $appPassTwrapper = $appPassSection.find( '.application-passwords-list-table-wrapper' ),
9 | $appPassTbody = $appPassSection.find( 'tbody' ),
10 | $appPassTrNoItems = $appPassTbody.find( '.no-items' ),
11 | $removeAllBtn = $( '#revoke-all-application-passwords' ),
12 | tmplNewAppPass = wp.template( 'new-application-password' ),
13 | tmplAppPassRow = wp.template( 'application-password-row' ),
14 | tmplNotice = wp.template( 'application-password-notice' ),
15 | testBasicAuthUser = Math.random().toString( 36 ).replace( /[^a-z]+/g, '' ),
16 | testBasicAuthPassword = Math.random().toString( 36 ).replace( /[^a-z]+/g, '' );
17 |
18 | $.ajax( {
19 | url: appPass.root + appPass.namespace + '/test-basic-authorization-header',
20 | method: 'POST',
21 | beforeSend: function( xhr ) {
22 | xhr.setRequestHeader( 'Authorization', 'Basic ' + btoa( testBasicAuthUser + ':' + testBasicAuthPassword ) );
23 | },
24 | error: function( jqXHR ) {
25 | if ( 404 === jqXHR.status ) {
26 | $newAppPassForm.before( tmplNotice( {
27 | type: 'error',
28 | message: appPass.text.no_credentials
29 | } ) );
30 | }
31 | }
32 | } ).done( function( response ) {
33 | if ( response.PHP_AUTH_USER === testBasicAuthUser && response.PHP_AUTH_PW === testBasicAuthPassword ) {
34 | // Save the success in SessionStorage or the like, so we don't do it on every page load?
35 | } else {
36 | $newAppPassForm.before( tmplNotice( {
37 | type: 'error',
38 | message: appPass.text.no_credentials
39 | } ) );
40 | }
41 | } );
42 |
43 | $newAppPassButton.click( function( e ) {
44 | e.preventDefault();
45 | var name = $newAppPassField.val();
46 |
47 | if ( 0 === name.length ) {
48 | $newAppPassField.focus();
49 | return;
50 | }
51 |
52 | $newAppPassField.prop( 'disabled', true );
53 | $newAppPassButton.prop( 'disabled', true );
54 |
55 | $.ajax( {
56 | url: appPass.root + appPass.namespace + '/application-passwords/' + appPass.user_id + '/add',
57 | method: 'POST',
58 | beforeSend: function( xhr ) {
59 | xhr.setRequestHeader( 'X-WP-Nonce', appPass.nonce );
60 | },
61 | data: {
62 | name: name
63 | }
64 | } ).done( function( response ) {
65 | $newAppPassField.prop( 'disabled', false ).val( '' );
66 | $newAppPassButton.prop( 'disabled', false );
67 |
68 | $newAppPassForm.after( tmplNewAppPass( {
69 | name: name,
70 | password: response.password
71 | } ) );
72 |
73 | $appPassTbody.prepend( tmplAppPassRow( response.row ) );
74 |
75 | $appPassTwrapper.show();
76 | $appPassTrNoItems.remove();
77 | } );
78 | } );
79 |
80 | $appPassTbody.on( 'click', '.delete', function( e ) {
81 | e.preventDefault();
82 |
83 | if ( ! confirm( appPass.text.revoke_password ) ) {
84 | return;
85 | }
86 |
87 | var $tr = $( e.target ).closest( 'tr' ),
88 | slug = $tr.data( 'slug' );
89 |
90 | $.ajax( {
91 | url: appPass.root + appPass.namespace + '/application-passwords/' + appPass.user_id + '/' + slug,
92 | method: 'DELETE',
93 | beforeSend: function( xhr ) {
94 | xhr.setRequestHeader( 'X-WP-Nonce', appPass.nonce );
95 | }
96 | } ).done( function( response ) {
97 | if ( response ) {
98 | if ( 0 === $tr.siblings().length ) {
99 | $appPassTwrapper.hide();
100 | }
101 | $tr.remove();
102 | }
103 | } );
104 | } );
105 |
106 | $removeAllBtn.on( 'click', function( e ) {
107 | e.preventDefault();
108 |
109 | if ( ! confirm( appPass.text.revoke_all_passwords ) ) {
110 | return;
111 | }
112 |
113 | $.ajax( {
114 | url: appPass.root + appPass.namespace + '/application-passwords/' + appPass.user_id,
115 | method: 'DELETE',
116 | beforeSend: function( xhr ) {
117 | xhr.setRequestHeader( 'X-WP-Nonce', appPass.nonce );
118 | }
119 | } ).done( function( response ) {
120 | if ( parseInt( response, 10 ) > 0 ) {
121 | $appPassTbody.children().remove();
122 | $appPassSection.children( '.new-application-password' ).remove();
123 | $appPassTwrapper.hide();
124 | }
125 | } );
126 | } );
127 |
128 | $( document ).on( 'click', '.application-password-modal-dismiss', function( e ) {
129 | e.preventDefault();
130 |
131 | $( '.new-application-password.notification-dialog-wrap' ).hide();
132 | } );
133 |
134 | // If there are no items, don't display the table yet. If there are, show it.
135 | if ( 0 === $appPassTbody.children( 'tr' ).not( $appPassTrNoItems ).length ) {
136 | $appPassTwrapper.hide();
137 | }
138 | }( jQuery, appPass ) );
139 |
--------------------------------------------------------------------------------
/application-passwords.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WordPress/application-passwords/2e6c19ab51daefe442e5b5b8e695a12c5982f6c4/assets/screenshot-1.png
--------------------------------------------------------------------------------
/assets/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WordPress/application-passwords/2e6c19ab51daefe442e5b5b8e695a12c5982f6c4/assets/screenshot-2.png
--------------------------------------------------------------------------------
/auth-app.js:
--------------------------------------------------------------------------------
1 | /* global authApp */
2 | ( function( $, authApp ) {
3 | var $appNameField = $( '#app_name' ),
4 | $approveBtn = $( '#approve' ),
5 | $rejectBtn = $( '#reject' ),
6 | $form = $appNameField.closest( 'form' );
7 |
8 | $approveBtn.click( function( e ) {
9 | var name = $appNameField.val();
10 |
11 | e.preventDefault();
12 |
13 | if ( 0 === name.length ) {
14 | $appNameField.focus();
15 | return;
16 | }
17 |
18 | $appNameField.prop( 'disabled', true );
19 | $approveBtn.prop( 'disabled', true );
20 |
21 | $.ajax( {
22 | url: authApp.root + authApp.namespace + '/application-passwords/' + authApp.user_id + '/add',
23 | method: 'POST',
24 | beforeSend: function( xhr ) {
25 | xhr.setRequestHeader( 'X-WP-Nonce', authApp.nonce );
26 | },
27 | data: {
28 | name: name
29 | }
30 | } ).done( function( response ) {
31 | var raw = authApp.success,
32 | url, $display;
33 |
34 | if ( raw ) {
35 | url = raw + ( -1 === raw.indexOf( '?' ) ? '?' : '&' ) +
36 | 'user_login=' + encodeURIComponent( authApp.user_login ) +
37 | '&password=' + encodeURIComponent( response.password );
38 |
39 | window.location = url;
40 | } else {
41 | // Should we maybe just reuse the js template modal from the profile page?
42 | $form.replaceWith( '
' +
43 | authApp.strings.new_pass
44 | .replace( '%1$s', '' )
45 | .replace( '%2$s', '' ) +
46 | '
' );
47 |
48 | $display = $( '.js-password-display' );
49 |
50 | // We're using .text() to write the variables to avoid any chance of XSS.
51 | $display.find( 'strong' ).text( name );
52 | $display.find( 'kbd' ).text( response.password );
53 | }
54 | } );
55 | } );
56 |
57 | $rejectBtn.click( function( e ) {
58 | e.preventDefault();
59 |
60 | // @todo: Make a better way to do this so it feels like less of a semi-open redirect.
61 | window.location = authApp.reject;
62 | } );
63 |
64 | $form.on( 'submit', function( e ) {
65 | e.preventDefault();
66 | } );
67 | }( jQuery, authApp ) );
68 |
--------------------------------------------------------------------------------
/class.application-passwords-list-table.php:
--------------------------------------------------------------------------------
1 | wp_strip_all_tags( __( 'Name' ) ),
27 | 'created' => wp_strip_all_tags( __( 'Created' ) ),
28 | 'last_used' => wp_strip_all_tags( __( 'Last Used' ) ),
29 | 'last_ip' => wp_strip_all_tags( __( 'Last IP' ) ),
30 | 'revoke' => wp_strip_all_tags( __( 'Revoke' ) ),
31 | );
32 | }
33 |
34 | /**
35 | * Prepares the list of items for displaying.
36 | *
37 | * @since 0.1-dev
38 | */
39 | public function prepare_items() {
40 | $columns = $this->get_columns();
41 | $hidden = array();
42 | $sortable = array();
43 | $primary = 'name';
44 | $this->_column_headers = array( $columns, $hidden, $sortable, $primary );
45 | }
46 |
47 | /**
48 | * Generates content for a single row of the table
49 | *
50 | * @since 0.1-dev
51 | * @access protected
52 | *
53 | * @param object $item The current item.
54 | * @param string $column_name The current column name.
55 | */
56 | protected function column_default( $item, $column_name ) {
57 | switch ( $column_name ) {
58 | case 'name':
59 | return esc_html( $item['name'] );
60 | case 'created':
61 | if ( empty( $item['created'] ) ) {
62 | return '—';
63 | }
64 | return date( get_option( 'date_format', 'r' ), $item['created'] );
65 | case 'last_used':
66 | if ( empty( $item['last_used'] ) ) {
67 | return '—';
68 | }
69 | return date( get_option( 'date_format', 'r' ), $item['last_used'] );
70 | case 'last_ip':
71 | if ( empty( $item['last_ip'] ) ) {
72 | return '—';
73 | }
74 | return $item['last_ip'];
75 | case 'revoke':
76 | return get_submit_button( __( 'Revoke' ), 'delete', 'revoke-application-password', false );
77 | default:
78 | return 'WTF^^?';
79 | }
80 | }
81 |
82 | /**
83 | * Generates custom table navigation to prevent conflicting nonces.
84 | *
85 | * @since 0.1-dev
86 | * @access protected
87 | *
88 | * @param string $which The location of the bulk actions: 'top' or 'bottom'.
89 | */
90 | protected function display_tablenav( $which ) {
91 | ?>
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | bulk_actions( $which ); ?>
102 |
103 | extra_tablenav( $which );
105 | $this->pagination( $which );
106 | ?>
107 |
108 |
109 |
110 | ';
122 | $this->single_row_columns( $item );
123 | echo '';
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/class.application-passwords.php:
--------------------------------------------------------------------------------
1 | ID ) {
61 | /*
62 | * For our authentication to work, we need to remove the cached lack
63 | * of a current user, so the next time it checks, we can detect that
64 | * this is a rest api request and allow our override to happen. This
65 | * is because the constant is defined later than the first get current
66 | * user call may run.
67 | */
68 | $current_user = null;
69 | }
70 | return $class;
71 | }
72 |
73 | /**
74 | * Handle declaration of REST API endpoints.
75 | *
76 | * @since 0.1-dev
77 | *
78 | * @access public
79 | * @static
80 | */
81 | public static function rest_api_init() {
82 | // List existing application passwords.
83 | register_rest_route( '2fa/v1', '/application-passwords/(?P[\d]+)', array(
84 | 'methods' => WP_REST_Server::READABLE,
85 | 'callback' => __CLASS__ . '::rest_list_application_passwords',
86 | 'permission_callback' => __CLASS__ . '::rest_edit_user_callback',
87 | ) );
88 |
89 | // Add new application passwords.
90 | register_rest_route( '2fa/v1', '/application-passwords/(?P[\d]+)/add', array(
91 | 'methods' => WP_REST_Server::CREATABLE,
92 | 'callback' => __CLASS__ . '::rest_add_application_password',
93 | 'permission_callback' => __CLASS__ . '::rest_edit_user_callback',
94 | 'args' => array(
95 | 'name' => array(
96 | 'required' => true,
97 | ),
98 | ),
99 | ) );
100 |
101 | // Delete an application password.
102 | register_rest_route( '2fa/v1', '/application-passwords/(?P[\d]+)/(?P[\da-fA-F]{12})', array(
103 | 'methods' => WP_REST_Server::DELETABLE,
104 | 'callback' => __CLASS__ . '::rest_delete_application_password',
105 | 'permission_callback' => __CLASS__ . '::rest_edit_user_callback',
106 | ) );
107 |
108 | // Delete all application passwords for a given user.
109 | register_rest_route( '2fa/v1', '/application-passwords/(?P[\d]+)', array(
110 | 'methods' => WP_REST_Server::DELETABLE,
111 | 'callback' => __CLASS__ . '::rest_delete_all_application_passwords',
112 | 'permission_callback' => __CLASS__ . '::rest_edit_user_callback',
113 | ) );
114 |
115 | // Some hosts that run PHP in FastCGI mode won't be given the Authentication header.
116 | register_rest_route( '2fa/v1', '/test-basic-authorization-header/', array(
117 | 'methods' => WP_REST_Server::READABLE . ', ' . WP_REST_Server::CREATABLE,
118 | 'callback' => __CLASS__ . '::rest_test_basic_authorization_header',
119 | 'permission_callback' => '__return_true',
120 | ) );
121 | }
122 |
123 | /**
124 | * REST API endpoint to list existing application passwords for a user.
125 | *
126 | * @since 0.1-dev
127 | *
128 | * @access public
129 | * @static
130 | *
131 | * @param $data
132 | *
133 | * @return array
134 | */
135 | public static function rest_list_application_passwords( $data ) {
136 | $application_passwords = self::get_user_application_passwords( $data['user_id'] );
137 | $with_slugs = array();
138 |
139 | if ( $application_passwords ) {
140 | foreach ( $application_passwords as $item ) {
141 | $item['slug'] = self::password_unique_slug( $item );
142 | unset( $item['raw'] );
143 | unset( $item['password'] );
144 |
145 | $item['created'] = date( get_option( 'date_format', 'r' ), $item['created'] );
146 |
147 | if ( empty( $item['last_used'] ) ) {
148 | $item['last_used'] = '—';
149 | } else {
150 | $item['last_used'] = date( get_option( 'date_format', 'r' ), $item['last_used'] );
151 | }
152 |
153 | if ( empty( $item['last_ip'] ) ) {
154 | $item['last_ip'] = '—';
155 | }
156 |
157 | $with_slugs[ $item['slug'] ] = $item;
158 | }
159 | }
160 |
161 | return $with_slugs;
162 | }
163 |
164 | /**
165 | * REST API endpoint to add a new application password for a user.
166 | *
167 | * @since 0.1-dev
168 | *
169 | * @access public
170 | * @static
171 | *
172 | * @param $data
173 | *
174 | * @return array
175 | */
176 | public static function rest_add_application_password( $data ) {
177 | list( $new_password, $new_item ) = self::create_new_application_password( $data['user_id'], $data['name'] );
178 |
179 | // Some tidying before we return it.
180 | $new_item['slug'] = self::password_unique_slug( $new_item );
181 | $new_item['created'] = date( get_option( 'date_format', 'r' ), $new_item['created'] );
182 | $new_item['last_used'] = '—';
183 | $new_item['last_ip'] = '—';
184 | unset( $new_item['password'] );
185 |
186 | return array(
187 | 'row' => $new_item,
188 | 'password' => self::chunk_password( $new_password )
189 | );
190 | }
191 |
192 | /**
193 | * REST API endpoint to delete a given application password.
194 | *
195 | * @since 0.1-dev
196 | *
197 | * @access public
198 | * @static
199 | *
200 | * @param $data
201 | *
202 | * @return bool
203 | */
204 | public static function rest_delete_application_password( $data ) {
205 | return self::delete_application_password( $data['user_id'], $data['slug'] );
206 | }
207 |
208 | /**
209 | * REST API endpoint to delete all of a user's application passwords.
210 | *
211 | * @since 0.1-dev
212 | *
213 | * @access public
214 | * @static
215 | *
216 | * @param $data
217 | *
218 | * @return int The number of deleted passwords
219 | */
220 | public static function rest_delete_all_application_passwords( $data ) {
221 | return self::delete_all_application_passwords( $data['user_id'] );
222 | }
223 |
224 | /**
225 | * Whether or not the current user can edit the specified user.
226 | *
227 | * @since 0.1-dev
228 | *
229 | * @access public
230 | * @static
231 | *
232 | * @param $data
233 | *
234 | * @return bool
235 | */
236 | public static function rest_edit_user_callback( $data ) {
237 | return current_user_can( 'edit_user', $data['user_id'] );
238 | }
239 |
240 | /**
241 | * Loosely Based on https://github.com/WP-API/Basic-Auth/blob/master/basic-auth.php
242 | *
243 | * @since 0.1-dev
244 | *
245 | * @access public
246 | * @static
247 | *
248 | * @param $input_user
249 | *
250 | * @return WP_User|bool
251 | */
252 | public static function rest_api_auth_handler( $input_user ){
253 | // Don't authenticate twice.
254 | if ( ! empty( $input_user ) ) {
255 | return $input_user;
256 | }
257 |
258 | // Check that we're trying to authenticate
259 | if ( ! isset( $_SERVER['PHP_AUTH_USER'] ) ) {
260 | return $input_user;
261 | }
262 |
263 | $user = self::authenticate( $input_user, $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] );
264 |
265 | if ( $user instanceof WP_User ) {
266 | return $user->ID;
267 | }
268 |
269 | // If it wasn't a user what got returned, just pass on what we had received originally.
270 | return $input_user;
271 | }
272 |
273 | /**
274 | * Test whether PHP can see Basic Authorization headers passed to the web server.
275 | *
276 | * @return WP_Error|array
277 | */
278 | public static function rest_test_basic_authorization_header() {
279 | $response = array();
280 |
281 | if ( isset( $_SERVER['PHP_AUTH_USER'] ) ) {
282 | $response['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER'];
283 | }
284 |
285 | if ( isset( $_SERVER['PHP_AUTH_PW'] ) ) {
286 | $response['PHP_AUTH_PW'] = $_SERVER['PHP_AUTH_PW'];
287 | }
288 |
289 | if ( empty( $response ) ) {
290 | return new WP_Error( 'no-credentials', __( 'No HTTP Basic Authorization credentials were found submitted with this request.' ), array( 'status' => 404 ) );
291 | }
292 |
293 | return $response;
294 | }
295 |
296 | /**
297 | * Some servers running in CGI or FastCGI mode don't pass the Authorization
298 | * header on to WordPress. If it's been rewritten to the `REMOTE_USER` header,
299 | * fill in the proper $_SERVER variables instead.
300 | */
301 | public static function fallback_populate_username_password() {
302 | // If we don't have anything to pull from, return early.
303 | if ( ! isset( $_SERVER['REMOTE_USER'] ) && ! isset( $_SERVER['REDIRECT_REMOTE_USER'] ) ) {
304 | return;
305 | }
306 |
307 | // If either PHP_AUTH key is already set, do nothing.
308 | if ( isset( $_SERVER['PHP_AUTH_USER'] ) || isset( $_SERVER['PHP_AUTH_PW'] ) ) {
309 | return;
310 | }
311 |
312 | // From our prior conditional, one of these must be set.
313 | $header = isset( $_SERVER['REMOTE_USER'] ) ? $_SERVER['REMOTE_USER'] : $_SERVER['REDIRECT_REMOTE_USER'];
314 |
315 | // Test to make sure the pattern matches expected.
316 | if ( ! preg_match( '%^Basic [a-z\d/+]*={0,2}$%i', $header ) ) {
317 | return;
318 | }
319 |
320 | // Removing `Basic ` the token would start six characters in.
321 | $token = substr( $header, 6 );
322 | $userpass = base64_decode( $token );
323 | list( $user, $pass ) = explode( ':', $userpass );
324 |
325 | // Now shove them in the proper keys where we're expecting later on.
326 | $_SERVER['PHP_AUTH_USER'] = $user;
327 | $_SERVER['PHP_AUTH_PW'] = $pass;
328 |
329 | return array( $user, $pass );
330 | }
331 |
332 | /**
333 | * Check if the current request is an API request
334 | * for which we should check the HTTP Auth headers.
335 | *
336 | * @return boolean
337 | */
338 | public static function is_api_request() {
339 | // Process the authentication only after the APIs have been initialized.
340 | return ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) );
341 | }
342 |
343 | /**
344 | * Filter the user to authenticate.
345 | *
346 | * @since 0.1-dev
347 | *
348 | * @access public
349 | * @static
350 | *
351 | * @param WP_User $input_user User to authenticate.
352 | * @param string $username User login.
353 | * @param string $password User password.
354 | *
355 | * @return mixed
356 | */
357 | public static function authenticate( $input_user, $username, $password ) {
358 | if ( ! apply_filters( 'application_password_is_api_request', self::is_api_request() ) ) {
359 | return $input_user;
360 | }
361 |
362 | $user = get_user_by( 'login', $username );
363 |
364 | if ( ! $user && is_email( $username ) ) {
365 | $user = get_user_by( 'email', $username );
366 | }
367 |
368 | // If the login name is invalid, short circuit.
369 | if ( ! $user ) {
370 | return $input_user;
371 | }
372 |
373 | /*
374 | * Strip out anything non-alphanumeric. This is so passwords can be used with
375 | * or without spaces to indicate the groupings for readability.
376 | *
377 | * Generated application passwords are exclusively alphanumeric.
378 | */
379 | $password = preg_replace( '/[^a-z\d]/i', '', $password );
380 |
381 | $hashed_passwords = get_user_meta( $user->ID, self::USERMETA_KEY_APPLICATION_PASSWORDS, true );
382 |
383 | // If there aren't any, there's nothing to return. Avoid the foreach.
384 | if ( empty( $hashed_passwords ) ) {
385 | return $input_user;
386 | }
387 |
388 | foreach ( $hashed_passwords as $key => $item ) {
389 | if ( wp_check_password( $password, $item['password'], $user->ID ) ) {
390 | $item['last_used'] = time();
391 | $item['last_ip'] = $_SERVER['REMOTE_ADDR'];
392 | $hashed_passwords[ $key ] = $item;
393 | update_user_meta( $user->ID, self::USERMETA_KEY_APPLICATION_PASSWORDS, $hashed_passwords );
394 |
395 | do_action( 'application_password_did_authenticate', $user, $item );
396 |
397 | return $user;
398 | }
399 | }
400 |
401 | // By default, return what we've been passed.
402 | return $input_user;
403 | }
404 |
405 | /**
406 | * Registers the hidden admin page to handle auth.
407 | */
408 | public static function admin_menu() {
409 | add_submenu_page( null, __( 'Approve Application' ), null, 'exist', 'auth_app', array( __CLASS__, 'auth_app_page' ) );
410 | }
411 |
412 | /**
413 | * Page for authorizing applications.
414 | */
415 | public static function auth_app_page() {
416 | $app_name = ! empty( $_GET['app_name'] ) ? $_GET['app_name'] : ''; // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
417 | $success_url = ! empty( $_GET['success_url'] ) ? $_GET['success_url'] : null; // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
418 | $reject_url = ! empty( $_GET['reject_url'] ) ? $_GET['reject_url'] : $success_url; // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
419 | $user = wp_get_current_user();
420 |
421 | wp_enqueue_script( 'auth-app', plugin_dir_url( __FILE__ ) . 'auth-app.js', array(), APPLICATION_PASSWORDS_VERSION, true );
422 | wp_localize_script(
423 | 'auth-app',
424 | 'authApp',
425 | array(
426 | 'root' => esc_url_raw( rest_url() ),
427 | 'namespace' => '2fa/v1',
428 | 'nonce' => wp_create_nonce( 'wp_rest' ),
429 | 'user_id' => $user->ID,
430 | 'user_login' => $user->user_login,
431 | 'success' => $success_url,
432 | 'reject' => $reject_url ? $reject_url : admin_url(),
433 | 'strings' => array(
434 | // translators: application, password.
435 | 'new_pass' => esc_html_x( 'Your new password for %1$s is: %2$s', 'application, password' ),
436 | ),
437 | )
438 | );
439 | ?>
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 | ' . esc_html( $app_name ) . '' );
450 | ?>
451 |
452 |
453 |
454 |
455 |
512 |
513 |
514 | ' . esc_html__( 'Your New Application Password:' ) . '' . esc_html( self::chunk_password( $new_password ) ) . '
' );
537 | }
538 | $redirect = add_query_arg(
539 | array(
540 | 'username' => wp_get_current_user()->user_login,
541 | 'password' => $new_password,
542 | ),
543 | $success_url
544 | );
545 | }
546 |
547 | wp_redirect( $redirect ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
548 | exit;
549 |
550 | }
551 |
552 | /**
553 | * Display the application password section in a users profile.
554 | *
555 | * This executes during the `show_user_security_settings` action.
556 | *
557 | * @since 0.1-dev
558 | *
559 | * @access public
560 | * @static
561 | *
562 | * @param WP_User $user WP_User object of the logged-in user.
563 | */
564 | public static function show_user_profile( $user ) {
565 | wp_enqueue_style( 'application-passwords-css', plugin_dir_url( __FILE__ ) . 'application-passwords.css', array(), APPLICATION_PASSWORDS_VERSION );
566 | wp_enqueue_script( 'application-passwords-js', plugin_dir_url( __FILE__ ) . 'application-passwords.js', array(), APPLICATION_PASSWORDS_VERSION, true );
567 | wp_localize_script(
568 | 'application-passwords-js',
569 | 'appPass',
570 | array(
571 | 'root' => esc_url_raw( rest_url() ),
572 | 'namespace' => '2fa/v1',
573 | 'nonce' => wp_create_nonce( 'wp_rest' ),
574 | 'user_id' => $user->ID,
575 | 'text' => array(
576 | 'no_credentials' => __( 'Due to a potential server misconfiguration, it seems that HTTP Basic Authorization may not work for the REST API on this site: `Authorization` headers are not being sent to WordPress by the web server. You can learn more about this problem, and a possible solution, on our GitHub Wiki.' ),
577 | 'revoke_password' => esc_attr__( 'Are you sure you want to revoke this password? This action cannot be undone.' ),
578 | 'revoke_all_passwords' => esc_attr__( 'Are you sure you want to revoke all passwords? This action cannot be undone.' ),
579 | ),
580 | )
581 | );
582 |
583 | ?>
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 | items = array_reverse( self::get_user_application_passwords( $user->ID ) );
597 | $application_passwords_list_table->prepare_items();
598 | $application_passwords_list_table->display();
599 | ?>
600 |
601 |
602 |
603 |
623 |
624 |
644 |
645 |
648 | $name,
669 | 'password' => $hashed_password,
670 | 'created' => time(),
671 | 'last_used' => null,
672 | 'last_ip' => null,
673 | );
674 |
675 | $passwords = self::get_user_application_passwords( $user_id );
676 | if ( ! $passwords ) {
677 | $passwords = array();
678 | }
679 |
680 | $passwords[] = $new_item;
681 | self::set_user_application_passwords( $user_id, $passwords );
682 |
683 | return array( $new_password, $new_item );
684 | }
685 |
686 | /**
687 | * Delete a specified application password.
688 | *
689 | * @since 0.1-dev
690 | *
691 | * @access public
692 | * @static
693 | *
694 | * @see Application_Passwords::password_unique_slug()
695 | *
696 | * @param int $user_id User ID.
697 | * @param string $slug The generated slug of the password in question.
698 | * @return bool Whether the password was successfully found and deleted.
699 | */
700 | public static function delete_application_password( $user_id, $slug ) {
701 | $passwords = self::get_user_application_passwords( $user_id );
702 |
703 | foreach ( $passwords as $key => $item ) {
704 | if ( self::password_unique_slug( $item ) === $slug ) {
705 | unset( $passwords[ $key ] );
706 | self::set_user_application_passwords( $user_id, $passwords );
707 | return true;
708 | }
709 | }
710 |
711 | // Specified Application Password not found!
712 | return false;
713 | }
714 |
715 | /**
716 | * Deletes all application passwords for the given user.
717 | *
718 | * @since 0.1-dev
719 | *
720 | * @access public
721 | * @static
722 | *
723 | * @param int $user_id User ID.
724 | * @return int The number of passwords that were deleted.
725 | */
726 | public static function delete_all_application_passwords( $user_id ) {
727 | $passwords = self::get_user_application_passwords( $user_id );
728 |
729 | if ( is_array( $passwords ) ) {
730 | self::set_user_application_passwords( $user_id, array() );
731 | return sizeof( $passwords );
732 | }
733 |
734 | return 0;
735 | }
736 |
737 | /**
738 | * Generate a unique repeatable slug from the hashed password, name, and when it was created.
739 | *
740 | * @since 0.1-dev
741 | *
742 | * @access public
743 | * @static
744 | *
745 | * @param array $item The current item.
746 | * @return string
747 | */
748 | public static function password_unique_slug( $item ) {
749 | $concat = $item['name'] . '|' . $item['password'] . '|' . $item['created'];
750 | $hash = md5( $concat );
751 | return substr( $hash, 0, 12 );
752 | }
753 |
754 | /**
755 | * Sanitize and then split a password into smaller chunks.
756 | *
757 | * @since 0.1-dev
758 | *
759 | * @access public
760 | * @static
761 | *
762 | * @param string $raw_password Users raw password.
763 | * @return string
764 | */
765 | public static function chunk_password( $raw_password ) {
766 | $raw_password = preg_replace( '/[^a-z\d]/i', '', $raw_password );
767 | return trim( chunk_split( $raw_password, 4, ' ' ) );
768 | }
769 |
770 | /**
771 | * Get a users application passwords.
772 | *
773 | * @since 0.1-dev
774 | *
775 | * @access public
776 | * @static
777 | *
778 | * @param int $user_id User ID.
779 | * @return array
780 | */
781 | public static function get_user_application_passwords( $user_id ) {
782 | $passwords = get_user_meta( $user_id, self::USERMETA_KEY_APPLICATION_PASSWORDS, true );
783 | if ( ! is_array( $passwords ) ) {
784 | return array();
785 | }
786 | return $passwords;
787 | }
788 |
789 | /**
790 | * Set a users application passwords.
791 | *
792 | * @since 0.1-dev
793 | *
794 | * @access public
795 | * @static
796 | *
797 | * @param int $user_id User ID.
798 | * @param array $passwords Application passwords.
799 | *
800 | * @return bool
801 | */
802 | public static function set_user_application_passwords( $user_id, $passwords ) {
803 | return update_user_meta( $user_id, self::USERMETA_KEY_APPLICATION_PASSWORDS, $passwords );
804 | }
805 | }
806 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "georgestephanis/application-passwords",
3 | "description": "Provide Application Passwords for WordPress core",
4 | "homepage": "https://github.com/WordPress/application-passwords",
5 | "keywords": [
6 | "wordpress",
7 | "application Ppasswords",
8 | "api"
9 | ],
10 | "type": "wordpress-plugin",
11 | "license": "GPL-2.0-or-later",
12 | "authors": [
13 | {
14 | "name": "George Stephanis",
15 | "email": "georgestephanis@automattic.com",
16 | "homepage": "https://github.com/georgestephanis"
17 | }
18 | ],
19 | "support": {
20 | "issues": "https://github.com/WordPress/application-passwords/issues"
21 | },
22 | "config": {
23 | "platform": {
24 | "php": "5.6"
25 | }
26 | },
27 | "require": {
28 | "composer/installers": "~1.0"
29 | },
30 | "require-dev": {
31 | "xwp/wp-dev-lib": "^1.2",
32 | "wp-coding-standards/wpcs": "^2.1",
33 | "phpunit/phpunit": "^4.8",
34 | "wpsh/local": "^0.2.3"
35 | },
36 | "scripts": {
37 | "lint-staged": [
38 | "DEV_LIB_SKIP=phpunit DIFF_BASE=master SYNC_README_MD=0 ./vendor/xwp/wp-dev-lib/scripts/pre-commit"
39 | ],
40 | "lint-head": [
41 | "DEV_LIB_SKIP=phpunit DIFF_BASE=master DIFF_HEAD=HEAD SYNC_README_MD=0 ./vendor/xwp/wp-dev-lib/scripts/pre-commit"
42 | ],
43 | "lint": [
44 | "phpcs ."
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "d33bf18b29d80d32d40525d0f6e5c2e5",
8 | "packages": [
9 | {
10 | "name": "composer/installers",
11 | "version": "v1.7.0",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/composer/installers.git",
15 | "reference": "141b272484481432cda342727a427dc1e206bfa0"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/composer/installers/zipball/141b272484481432cda342727a427dc1e206bfa0",
20 | "reference": "141b272484481432cda342727a427dc1e206bfa0",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "composer-plugin-api": "^1.0"
25 | },
26 | "replace": {
27 | "roundcube/plugin-installer": "*",
28 | "shama/baton": "*"
29 | },
30 | "require-dev": {
31 | "composer/composer": "1.0.*@dev",
32 | "phpunit/phpunit": "^4.8.36"
33 | },
34 | "type": "composer-plugin",
35 | "extra": {
36 | "class": "Composer\\Installers\\Plugin",
37 | "branch-alias": {
38 | "dev-master": "1.0-dev"
39 | }
40 | },
41 | "autoload": {
42 | "psr-4": {
43 | "Composer\\Installers\\": "src/Composer/Installers"
44 | }
45 | },
46 | "notification-url": "https://packagist.org/downloads/",
47 | "license": [
48 | "MIT"
49 | ],
50 | "authors": [
51 | {
52 | "name": "Kyle Robinson Young",
53 | "email": "kyle@dontkry.com",
54 | "homepage": "https://github.com/shama"
55 | }
56 | ],
57 | "description": "A multi-framework Composer library installer",
58 | "homepage": "https://composer.github.io/installers/",
59 | "keywords": [
60 | "Craft",
61 | "Dolibarr",
62 | "Eliasis",
63 | "Hurad",
64 | "ImageCMS",
65 | "Kanboard",
66 | "Lan Management System",
67 | "MODX Evo",
68 | "Mautic",
69 | "Maya",
70 | "OXID",
71 | "Plentymarkets",
72 | "Porto",
73 | "RadPHP",
74 | "SMF",
75 | "Thelia",
76 | "Whmcs",
77 | "WolfCMS",
78 | "agl",
79 | "aimeos",
80 | "annotatecms",
81 | "attogram",
82 | "bitrix",
83 | "cakephp",
84 | "chef",
85 | "cockpit",
86 | "codeigniter",
87 | "concrete5",
88 | "croogo",
89 | "dokuwiki",
90 | "drupal",
91 | "eZ Platform",
92 | "elgg",
93 | "expressionengine",
94 | "fuelphp",
95 | "grav",
96 | "installer",
97 | "itop",
98 | "joomla",
99 | "known",
100 | "kohana",
101 | "laravel",
102 | "lavalite",
103 | "lithium",
104 | "magento",
105 | "majima",
106 | "mako",
107 | "mediawiki",
108 | "modulework",
109 | "modx",
110 | "moodle",
111 | "osclass",
112 | "phpbb",
113 | "piwik",
114 | "ppi",
115 | "puppet",
116 | "pxcms",
117 | "reindex",
118 | "roundcube",
119 | "shopware",
120 | "silverstripe",
121 | "sydes",
122 | "symfony",
123 | "typo3",
124 | "wordpress",
125 | "yawik",
126 | "zend",
127 | "zikula"
128 | ],
129 | "time": "2019-08-12T15:00:31+00:00"
130 | }
131 | ],
132 | "packages-dev": [
133 | {
134 | "name": "doctrine/instantiator",
135 | "version": "1.0.5",
136 | "source": {
137 | "type": "git",
138 | "url": "https://github.com/doctrine/instantiator.git",
139 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
140 | },
141 | "dist": {
142 | "type": "zip",
143 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
144 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
145 | "shasum": ""
146 | },
147 | "require": {
148 | "php": ">=5.3,<8.0-DEV"
149 | },
150 | "require-dev": {
151 | "athletic/athletic": "~0.1.8",
152 | "ext-pdo": "*",
153 | "ext-phar": "*",
154 | "phpunit/phpunit": "~4.0",
155 | "squizlabs/php_codesniffer": "~2.0"
156 | },
157 | "type": "library",
158 | "extra": {
159 | "branch-alias": {
160 | "dev-master": "1.0.x-dev"
161 | }
162 | },
163 | "autoload": {
164 | "psr-4": {
165 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
166 | }
167 | },
168 | "notification-url": "https://packagist.org/downloads/",
169 | "license": [
170 | "MIT"
171 | ],
172 | "authors": [
173 | {
174 | "name": "Marco Pivetta",
175 | "email": "ocramius@gmail.com",
176 | "homepage": "http://ocramius.github.com/"
177 | }
178 | ],
179 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
180 | "homepage": "https://github.com/doctrine/instantiator",
181 | "keywords": [
182 | "constructor",
183 | "instantiate"
184 | ],
185 | "time": "2015-06-14T21:17:01+00:00"
186 | },
187 | {
188 | "name": "phpdocumentor/reflection-common",
189 | "version": "1.0.1",
190 | "source": {
191 | "type": "git",
192 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
193 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
194 | },
195 | "dist": {
196 | "type": "zip",
197 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
198 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
199 | "shasum": ""
200 | },
201 | "require": {
202 | "php": ">=5.5"
203 | },
204 | "require-dev": {
205 | "phpunit/phpunit": "^4.6"
206 | },
207 | "type": "library",
208 | "extra": {
209 | "branch-alias": {
210 | "dev-master": "1.0.x-dev"
211 | }
212 | },
213 | "autoload": {
214 | "psr-4": {
215 | "phpDocumentor\\Reflection\\": [
216 | "src"
217 | ]
218 | }
219 | },
220 | "notification-url": "https://packagist.org/downloads/",
221 | "license": [
222 | "MIT"
223 | ],
224 | "authors": [
225 | {
226 | "name": "Jaap van Otterdijk",
227 | "email": "opensource@ijaap.nl"
228 | }
229 | ],
230 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
231 | "homepage": "http://www.phpdoc.org",
232 | "keywords": [
233 | "FQSEN",
234 | "phpDocumentor",
235 | "phpdoc",
236 | "reflection",
237 | "static analysis"
238 | ],
239 | "time": "2017-09-11T18:02:19+00:00"
240 | },
241 | {
242 | "name": "phpdocumentor/reflection-docblock",
243 | "version": "3.3.2",
244 | "source": {
245 | "type": "git",
246 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
247 | "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2"
248 | },
249 | "dist": {
250 | "type": "zip",
251 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2",
252 | "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2",
253 | "shasum": ""
254 | },
255 | "require": {
256 | "php": "^5.6 || ^7.0",
257 | "phpdocumentor/reflection-common": "^1.0.0",
258 | "phpdocumentor/type-resolver": "^0.4.0",
259 | "webmozart/assert": "^1.0"
260 | },
261 | "require-dev": {
262 | "mockery/mockery": "^0.9.4",
263 | "phpunit/phpunit": "^4.4"
264 | },
265 | "type": "library",
266 | "autoload": {
267 | "psr-4": {
268 | "phpDocumentor\\Reflection\\": [
269 | "src/"
270 | ]
271 | }
272 | },
273 | "notification-url": "https://packagist.org/downloads/",
274 | "license": [
275 | "MIT"
276 | ],
277 | "authors": [
278 | {
279 | "name": "Mike van Riel",
280 | "email": "me@mikevanriel.com"
281 | }
282 | ],
283 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
284 | "time": "2017-11-10T14:09:06+00:00"
285 | },
286 | {
287 | "name": "phpdocumentor/type-resolver",
288 | "version": "0.4.0",
289 | "source": {
290 | "type": "git",
291 | "url": "https://github.com/phpDocumentor/TypeResolver.git",
292 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
293 | },
294 | "dist": {
295 | "type": "zip",
296 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
297 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
298 | "shasum": ""
299 | },
300 | "require": {
301 | "php": "^5.5 || ^7.0",
302 | "phpdocumentor/reflection-common": "^1.0"
303 | },
304 | "require-dev": {
305 | "mockery/mockery": "^0.9.4",
306 | "phpunit/phpunit": "^5.2||^4.8.24"
307 | },
308 | "type": "library",
309 | "extra": {
310 | "branch-alias": {
311 | "dev-master": "1.0.x-dev"
312 | }
313 | },
314 | "autoload": {
315 | "psr-4": {
316 | "phpDocumentor\\Reflection\\": [
317 | "src/"
318 | ]
319 | }
320 | },
321 | "notification-url": "https://packagist.org/downloads/",
322 | "license": [
323 | "MIT"
324 | ],
325 | "authors": [
326 | {
327 | "name": "Mike van Riel",
328 | "email": "me@mikevanriel.com"
329 | }
330 | ],
331 | "time": "2017-07-14T14:27:02+00:00"
332 | },
333 | {
334 | "name": "phpspec/prophecy",
335 | "version": "1.10.1",
336 | "source": {
337 | "type": "git",
338 | "url": "https://github.com/phpspec/prophecy.git",
339 | "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc"
340 | },
341 | "dist": {
342 | "type": "zip",
343 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/cbe1df668b3fe136bcc909126a0f529a78d4cbbc",
344 | "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc",
345 | "shasum": ""
346 | },
347 | "require": {
348 | "doctrine/instantiator": "^1.0.2",
349 | "php": "^5.3|^7.0",
350 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
351 | "sebastian/comparator": "^1.2.3|^2.0|^3.0",
352 | "sebastian/recursion-context": "^1.0|^2.0|^3.0"
353 | },
354 | "require-dev": {
355 | "phpspec/phpspec": "^2.5 || ^3.2",
356 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
357 | },
358 | "type": "library",
359 | "extra": {
360 | "branch-alias": {
361 | "dev-master": "1.10.x-dev"
362 | }
363 | },
364 | "autoload": {
365 | "psr-4": {
366 | "Prophecy\\": "src/Prophecy"
367 | }
368 | },
369 | "notification-url": "https://packagist.org/downloads/",
370 | "license": [
371 | "MIT"
372 | ],
373 | "authors": [
374 | {
375 | "name": "Konstantin Kudryashov",
376 | "email": "ever.zet@gmail.com",
377 | "homepage": "http://everzet.com"
378 | },
379 | {
380 | "name": "Marcello Duarte",
381 | "email": "marcello.duarte@gmail.com"
382 | }
383 | ],
384 | "description": "Highly opinionated mocking framework for PHP 5.3+",
385 | "homepage": "https://github.com/phpspec/prophecy",
386 | "keywords": [
387 | "Double",
388 | "Dummy",
389 | "fake",
390 | "mock",
391 | "spy",
392 | "stub"
393 | ],
394 | "time": "2019-12-22T21:05:45+00:00"
395 | },
396 | {
397 | "name": "phpunit/php-code-coverage",
398 | "version": "2.2.4",
399 | "source": {
400 | "type": "git",
401 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
402 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
403 | },
404 | "dist": {
405 | "type": "zip",
406 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
407 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
408 | "shasum": ""
409 | },
410 | "require": {
411 | "php": ">=5.3.3",
412 | "phpunit/php-file-iterator": "~1.3",
413 | "phpunit/php-text-template": "~1.2",
414 | "phpunit/php-token-stream": "~1.3",
415 | "sebastian/environment": "^1.3.2",
416 | "sebastian/version": "~1.0"
417 | },
418 | "require-dev": {
419 | "ext-xdebug": ">=2.1.4",
420 | "phpunit/phpunit": "~4"
421 | },
422 | "suggest": {
423 | "ext-dom": "*",
424 | "ext-xdebug": ">=2.2.1",
425 | "ext-xmlwriter": "*"
426 | },
427 | "type": "library",
428 | "extra": {
429 | "branch-alias": {
430 | "dev-master": "2.2.x-dev"
431 | }
432 | },
433 | "autoload": {
434 | "classmap": [
435 | "src/"
436 | ]
437 | },
438 | "notification-url": "https://packagist.org/downloads/",
439 | "license": [
440 | "BSD-3-Clause"
441 | ],
442 | "authors": [
443 | {
444 | "name": "Sebastian Bergmann",
445 | "email": "sb@sebastian-bergmann.de",
446 | "role": "lead"
447 | }
448 | ],
449 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
450 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
451 | "keywords": [
452 | "coverage",
453 | "testing",
454 | "xunit"
455 | ],
456 | "time": "2015-10-06T15:47:00+00:00"
457 | },
458 | {
459 | "name": "phpunit/php-file-iterator",
460 | "version": "1.4.5",
461 | "source": {
462 | "type": "git",
463 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
464 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
465 | },
466 | "dist": {
467 | "type": "zip",
468 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
469 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
470 | "shasum": ""
471 | },
472 | "require": {
473 | "php": ">=5.3.3"
474 | },
475 | "type": "library",
476 | "extra": {
477 | "branch-alias": {
478 | "dev-master": "1.4.x-dev"
479 | }
480 | },
481 | "autoload": {
482 | "classmap": [
483 | "src/"
484 | ]
485 | },
486 | "notification-url": "https://packagist.org/downloads/",
487 | "license": [
488 | "BSD-3-Clause"
489 | ],
490 | "authors": [
491 | {
492 | "name": "Sebastian Bergmann",
493 | "email": "sb@sebastian-bergmann.de",
494 | "role": "lead"
495 | }
496 | ],
497 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
498 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
499 | "keywords": [
500 | "filesystem",
501 | "iterator"
502 | ],
503 | "time": "2017-11-27T13:52:08+00:00"
504 | },
505 | {
506 | "name": "phpunit/php-text-template",
507 | "version": "1.2.1",
508 | "source": {
509 | "type": "git",
510 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
511 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
512 | },
513 | "dist": {
514 | "type": "zip",
515 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
516 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
517 | "shasum": ""
518 | },
519 | "require": {
520 | "php": ">=5.3.3"
521 | },
522 | "type": "library",
523 | "autoload": {
524 | "classmap": [
525 | "src/"
526 | ]
527 | },
528 | "notification-url": "https://packagist.org/downloads/",
529 | "license": [
530 | "BSD-3-Clause"
531 | ],
532 | "authors": [
533 | {
534 | "name": "Sebastian Bergmann",
535 | "email": "sebastian@phpunit.de",
536 | "role": "lead"
537 | }
538 | ],
539 | "description": "Simple template engine.",
540 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
541 | "keywords": [
542 | "template"
543 | ],
544 | "time": "2015-06-21T13:50:34+00:00"
545 | },
546 | {
547 | "name": "phpunit/php-timer",
548 | "version": "1.0.9",
549 | "source": {
550 | "type": "git",
551 | "url": "https://github.com/sebastianbergmann/php-timer.git",
552 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
553 | },
554 | "dist": {
555 | "type": "zip",
556 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
557 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
558 | "shasum": ""
559 | },
560 | "require": {
561 | "php": "^5.3.3 || ^7.0"
562 | },
563 | "require-dev": {
564 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
565 | },
566 | "type": "library",
567 | "extra": {
568 | "branch-alias": {
569 | "dev-master": "1.0-dev"
570 | }
571 | },
572 | "autoload": {
573 | "classmap": [
574 | "src/"
575 | ]
576 | },
577 | "notification-url": "https://packagist.org/downloads/",
578 | "license": [
579 | "BSD-3-Clause"
580 | ],
581 | "authors": [
582 | {
583 | "name": "Sebastian Bergmann",
584 | "email": "sb@sebastian-bergmann.de",
585 | "role": "lead"
586 | }
587 | ],
588 | "description": "Utility class for timing",
589 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
590 | "keywords": [
591 | "timer"
592 | ],
593 | "time": "2017-02-26T11:10:40+00:00"
594 | },
595 | {
596 | "name": "phpunit/php-token-stream",
597 | "version": "1.4.12",
598 | "source": {
599 | "type": "git",
600 | "url": "https://github.com/sebastianbergmann/php-token-stream.git",
601 | "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
602 | },
603 | "dist": {
604 | "type": "zip",
605 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
606 | "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
607 | "shasum": ""
608 | },
609 | "require": {
610 | "ext-tokenizer": "*",
611 | "php": ">=5.3.3"
612 | },
613 | "require-dev": {
614 | "phpunit/phpunit": "~4.2"
615 | },
616 | "type": "library",
617 | "extra": {
618 | "branch-alias": {
619 | "dev-master": "1.4-dev"
620 | }
621 | },
622 | "autoload": {
623 | "classmap": [
624 | "src/"
625 | ]
626 | },
627 | "notification-url": "https://packagist.org/downloads/",
628 | "license": [
629 | "BSD-3-Clause"
630 | ],
631 | "authors": [
632 | {
633 | "name": "Sebastian Bergmann",
634 | "email": "sebastian@phpunit.de"
635 | }
636 | ],
637 | "description": "Wrapper around PHP's tokenizer extension.",
638 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
639 | "keywords": [
640 | "tokenizer"
641 | ],
642 | "time": "2017-12-04T08:55:13+00:00"
643 | },
644 | {
645 | "name": "phpunit/phpunit",
646 | "version": "4.8.36",
647 | "source": {
648 | "type": "git",
649 | "url": "https://github.com/sebastianbergmann/phpunit.git",
650 | "reference": "46023de9a91eec7dfb06cc56cb4e260017298517"
651 | },
652 | "dist": {
653 | "type": "zip",
654 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517",
655 | "reference": "46023de9a91eec7dfb06cc56cb4e260017298517",
656 | "shasum": ""
657 | },
658 | "require": {
659 | "ext-dom": "*",
660 | "ext-json": "*",
661 | "ext-pcre": "*",
662 | "ext-reflection": "*",
663 | "ext-spl": "*",
664 | "php": ">=5.3.3",
665 | "phpspec/prophecy": "^1.3.1",
666 | "phpunit/php-code-coverage": "~2.1",
667 | "phpunit/php-file-iterator": "~1.4",
668 | "phpunit/php-text-template": "~1.2",
669 | "phpunit/php-timer": "^1.0.6",
670 | "phpunit/phpunit-mock-objects": "~2.3",
671 | "sebastian/comparator": "~1.2.2",
672 | "sebastian/diff": "~1.2",
673 | "sebastian/environment": "~1.3",
674 | "sebastian/exporter": "~1.2",
675 | "sebastian/global-state": "~1.0",
676 | "sebastian/version": "~1.0",
677 | "symfony/yaml": "~2.1|~3.0"
678 | },
679 | "suggest": {
680 | "phpunit/php-invoker": "~1.1"
681 | },
682 | "bin": [
683 | "phpunit"
684 | ],
685 | "type": "library",
686 | "extra": {
687 | "branch-alias": {
688 | "dev-master": "4.8.x-dev"
689 | }
690 | },
691 | "autoload": {
692 | "classmap": [
693 | "src/"
694 | ]
695 | },
696 | "notification-url": "https://packagist.org/downloads/",
697 | "license": [
698 | "BSD-3-Clause"
699 | ],
700 | "authors": [
701 | {
702 | "name": "Sebastian Bergmann",
703 | "email": "sebastian@phpunit.de",
704 | "role": "lead"
705 | }
706 | ],
707 | "description": "The PHP Unit Testing framework.",
708 | "homepage": "https://phpunit.de/",
709 | "keywords": [
710 | "phpunit",
711 | "testing",
712 | "xunit"
713 | ],
714 | "time": "2017-06-21T08:07:12+00:00"
715 | },
716 | {
717 | "name": "phpunit/phpunit-mock-objects",
718 | "version": "2.3.8",
719 | "source": {
720 | "type": "git",
721 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
722 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
723 | },
724 | "dist": {
725 | "type": "zip",
726 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
727 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
728 | "shasum": ""
729 | },
730 | "require": {
731 | "doctrine/instantiator": "^1.0.2",
732 | "php": ">=5.3.3",
733 | "phpunit/php-text-template": "~1.2",
734 | "sebastian/exporter": "~1.2"
735 | },
736 | "require-dev": {
737 | "phpunit/phpunit": "~4.4"
738 | },
739 | "suggest": {
740 | "ext-soap": "*"
741 | },
742 | "type": "library",
743 | "extra": {
744 | "branch-alias": {
745 | "dev-master": "2.3.x-dev"
746 | }
747 | },
748 | "autoload": {
749 | "classmap": [
750 | "src/"
751 | ]
752 | },
753 | "notification-url": "https://packagist.org/downloads/",
754 | "license": [
755 | "BSD-3-Clause"
756 | ],
757 | "authors": [
758 | {
759 | "name": "Sebastian Bergmann",
760 | "email": "sb@sebastian-bergmann.de",
761 | "role": "lead"
762 | }
763 | ],
764 | "description": "Mock Object library for PHPUnit",
765 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
766 | "keywords": [
767 | "mock",
768 | "xunit"
769 | ],
770 | "abandoned": true,
771 | "time": "2015-10-02T06:51:40+00:00"
772 | },
773 | {
774 | "name": "sebastian/comparator",
775 | "version": "1.2.4",
776 | "source": {
777 | "type": "git",
778 | "url": "https://github.com/sebastianbergmann/comparator.git",
779 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
780 | },
781 | "dist": {
782 | "type": "zip",
783 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
784 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
785 | "shasum": ""
786 | },
787 | "require": {
788 | "php": ">=5.3.3",
789 | "sebastian/diff": "~1.2",
790 | "sebastian/exporter": "~1.2 || ~2.0"
791 | },
792 | "require-dev": {
793 | "phpunit/phpunit": "~4.4"
794 | },
795 | "type": "library",
796 | "extra": {
797 | "branch-alias": {
798 | "dev-master": "1.2.x-dev"
799 | }
800 | },
801 | "autoload": {
802 | "classmap": [
803 | "src/"
804 | ]
805 | },
806 | "notification-url": "https://packagist.org/downloads/",
807 | "license": [
808 | "BSD-3-Clause"
809 | ],
810 | "authors": [
811 | {
812 | "name": "Jeff Welch",
813 | "email": "whatthejeff@gmail.com"
814 | },
815 | {
816 | "name": "Volker Dusch",
817 | "email": "github@wallbash.com"
818 | },
819 | {
820 | "name": "Bernhard Schussek",
821 | "email": "bschussek@2bepublished.at"
822 | },
823 | {
824 | "name": "Sebastian Bergmann",
825 | "email": "sebastian@phpunit.de"
826 | }
827 | ],
828 | "description": "Provides the functionality to compare PHP values for equality",
829 | "homepage": "http://www.github.com/sebastianbergmann/comparator",
830 | "keywords": [
831 | "comparator",
832 | "compare",
833 | "equality"
834 | ],
835 | "time": "2017-01-29T09:50:25+00:00"
836 | },
837 | {
838 | "name": "sebastian/diff",
839 | "version": "1.4.3",
840 | "source": {
841 | "type": "git",
842 | "url": "https://github.com/sebastianbergmann/diff.git",
843 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
844 | },
845 | "dist": {
846 | "type": "zip",
847 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
848 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
849 | "shasum": ""
850 | },
851 | "require": {
852 | "php": "^5.3.3 || ^7.0"
853 | },
854 | "require-dev": {
855 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
856 | },
857 | "type": "library",
858 | "extra": {
859 | "branch-alias": {
860 | "dev-master": "1.4-dev"
861 | }
862 | },
863 | "autoload": {
864 | "classmap": [
865 | "src/"
866 | ]
867 | },
868 | "notification-url": "https://packagist.org/downloads/",
869 | "license": [
870 | "BSD-3-Clause"
871 | ],
872 | "authors": [
873 | {
874 | "name": "Kore Nordmann",
875 | "email": "mail@kore-nordmann.de"
876 | },
877 | {
878 | "name": "Sebastian Bergmann",
879 | "email": "sebastian@phpunit.de"
880 | }
881 | ],
882 | "description": "Diff implementation",
883 | "homepage": "https://github.com/sebastianbergmann/diff",
884 | "keywords": [
885 | "diff"
886 | ],
887 | "time": "2017-05-22T07:24:03+00:00"
888 | },
889 | {
890 | "name": "sebastian/environment",
891 | "version": "1.3.8",
892 | "source": {
893 | "type": "git",
894 | "url": "https://github.com/sebastianbergmann/environment.git",
895 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
896 | },
897 | "dist": {
898 | "type": "zip",
899 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
900 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
901 | "shasum": ""
902 | },
903 | "require": {
904 | "php": "^5.3.3 || ^7.0"
905 | },
906 | "require-dev": {
907 | "phpunit/phpunit": "^4.8 || ^5.0"
908 | },
909 | "type": "library",
910 | "extra": {
911 | "branch-alias": {
912 | "dev-master": "1.3.x-dev"
913 | }
914 | },
915 | "autoload": {
916 | "classmap": [
917 | "src/"
918 | ]
919 | },
920 | "notification-url": "https://packagist.org/downloads/",
921 | "license": [
922 | "BSD-3-Clause"
923 | ],
924 | "authors": [
925 | {
926 | "name": "Sebastian Bergmann",
927 | "email": "sebastian@phpunit.de"
928 | }
929 | ],
930 | "description": "Provides functionality to handle HHVM/PHP environments",
931 | "homepage": "http://www.github.com/sebastianbergmann/environment",
932 | "keywords": [
933 | "Xdebug",
934 | "environment",
935 | "hhvm"
936 | ],
937 | "time": "2016-08-18T05:49:44+00:00"
938 | },
939 | {
940 | "name": "sebastian/exporter",
941 | "version": "1.2.2",
942 | "source": {
943 | "type": "git",
944 | "url": "https://github.com/sebastianbergmann/exporter.git",
945 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
946 | },
947 | "dist": {
948 | "type": "zip",
949 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
950 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
951 | "shasum": ""
952 | },
953 | "require": {
954 | "php": ">=5.3.3",
955 | "sebastian/recursion-context": "~1.0"
956 | },
957 | "require-dev": {
958 | "ext-mbstring": "*",
959 | "phpunit/phpunit": "~4.4"
960 | },
961 | "type": "library",
962 | "extra": {
963 | "branch-alias": {
964 | "dev-master": "1.3.x-dev"
965 | }
966 | },
967 | "autoload": {
968 | "classmap": [
969 | "src/"
970 | ]
971 | },
972 | "notification-url": "https://packagist.org/downloads/",
973 | "license": [
974 | "BSD-3-Clause"
975 | ],
976 | "authors": [
977 | {
978 | "name": "Jeff Welch",
979 | "email": "whatthejeff@gmail.com"
980 | },
981 | {
982 | "name": "Volker Dusch",
983 | "email": "github@wallbash.com"
984 | },
985 | {
986 | "name": "Bernhard Schussek",
987 | "email": "bschussek@2bepublished.at"
988 | },
989 | {
990 | "name": "Sebastian Bergmann",
991 | "email": "sebastian@phpunit.de"
992 | },
993 | {
994 | "name": "Adam Harvey",
995 | "email": "aharvey@php.net"
996 | }
997 | ],
998 | "description": "Provides the functionality to export PHP variables for visualization",
999 | "homepage": "http://www.github.com/sebastianbergmann/exporter",
1000 | "keywords": [
1001 | "export",
1002 | "exporter"
1003 | ],
1004 | "time": "2016-06-17T09:04:28+00:00"
1005 | },
1006 | {
1007 | "name": "sebastian/global-state",
1008 | "version": "1.1.1",
1009 | "source": {
1010 | "type": "git",
1011 | "url": "https://github.com/sebastianbergmann/global-state.git",
1012 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
1013 | },
1014 | "dist": {
1015 | "type": "zip",
1016 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
1017 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
1018 | "shasum": ""
1019 | },
1020 | "require": {
1021 | "php": ">=5.3.3"
1022 | },
1023 | "require-dev": {
1024 | "phpunit/phpunit": "~4.2"
1025 | },
1026 | "suggest": {
1027 | "ext-uopz": "*"
1028 | },
1029 | "type": "library",
1030 | "extra": {
1031 | "branch-alias": {
1032 | "dev-master": "1.0-dev"
1033 | }
1034 | },
1035 | "autoload": {
1036 | "classmap": [
1037 | "src/"
1038 | ]
1039 | },
1040 | "notification-url": "https://packagist.org/downloads/",
1041 | "license": [
1042 | "BSD-3-Clause"
1043 | ],
1044 | "authors": [
1045 | {
1046 | "name": "Sebastian Bergmann",
1047 | "email": "sebastian@phpunit.de"
1048 | }
1049 | ],
1050 | "description": "Snapshotting of global state",
1051 | "homepage": "http://www.github.com/sebastianbergmann/global-state",
1052 | "keywords": [
1053 | "global state"
1054 | ],
1055 | "time": "2015-10-12T03:26:01+00:00"
1056 | },
1057 | {
1058 | "name": "sebastian/recursion-context",
1059 | "version": "1.0.5",
1060 | "source": {
1061 | "type": "git",
1062 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1063 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
1064 | },
1065 | "dist": {
1066 | "type": "zip",
1067 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
1068 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
1069 | "shasum": ""
1070 | },
1071 | "require": {
1072 | "php": ">=5.3.3"
1073 | },
1074 | "require-dev": {
1075 | "phpunit/phpunit": "~4.4"
1076 | },
1077 | "type": "library",
1078 | "extra": {
1079 | "branch-alias": {
1080 | "dev-master": "1.0.x-dev"
1081 | }
1082 | },
1083 | "autoload": {
1084 | "classmap": [
1085 | "src/"
1086 | ]
1087 | },
1088 | "notification-url": "https://packagist.org/downloads/",
1089 | "license": [
1090 | "BSD-3-Clause"
1091 | ],
1092 | "authors": [
1093 | {
1094 | "name": "Jeff Welch",
1095 | "email": "whatthejeff@gmail.com"
1096 | },
1097 | {
1098 | "name": "Sebastian Bergmann",
1099 | "email": "sebastian@phpunit.de"
1100 | },
1101 | {
1102 | "name": "Adam Harvey",
1103 | "email": "aharvey@php.net"
1104 | }
1105 | ],
1106 | "description": "Provides functionality to recursively process PHP variables",
1107 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
1108 | "time": "2016-10-03T07:41:43+00:00"
1109 | },
1110 | {
1111 | "name": "sebastian/version",
1112 | "version": "1.0.6",
1113 | "source": {
1114 | "type": "git",
1115 | "url": "https://github.com/sebastianbergmann/version.git",
1116 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
1117 | },
1118 | "dist": {
1119 | "type": "zip",
1120 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1121 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1122 | "shasum": ""
1123 | },
1124 | "type": "library",
1125 | "autoload": {
1126 | "classmap": [
1127 | "src/"
1128 | ]
1129 | },
1130 | "notification-url": "https://packagist.org/downloads/",
1131 | "license": [
1132 | "BSD-3-Clause"
1133 | ],
1134 | "authors": [
1135 | {
1136 | "name": "Sebastian Bergmann",
1137 | "email": "sebastian@phpunit.de",
1138 | "role": "lead"
1139 | }
1140 | ],
1141 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1142 | "homepage": "https://github.com/sebastianbergmann/version",
1143 | "time": "2015-06-21T13:59:46+00:00"
1144 | },
1145 | {
1146 | "name": "squizlabs/php_codesniffer",
1147 | "version": "3.5.3",
1148 | "source": {
1149 | "type": "git",
1150 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
1151 | "reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb"
1152 | },
1153 | "dist": {
1154 | "type": "zip",
1155 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/557a1fc7ac702c66b0bbfe16ab3d55839ef724cb",
1156 | "reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb",
1157 | "shasum": ""
1158 | },
1159 | "require": {
1160 | "ext-simplexml": "*",
1161 | "ext-tokenizer": "*",
1162 | "ext-xmlwriter": "*",
1163 | "php": ">=5.4.0"
1164 | },
1165 | "require-dev": {
1166 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
1167 | },
1168 | "bin": [
1169 | "bin/phpcs",
1170 | "bin/phpcbf"
1171 | ],
1172 | "type": "library",
1173 | "extra": {
1174 | "branch-alias": {
1175 | "dev-master": "3.x-dev"
1176 | }
1177 | },
1178 | "notification-url": "https://packagist.org/downloads/",
1179 | "license": [
1180 | "BSD-3-Clause"
1181 | ],
1182 | "authors": [
1183 | {
1184 | "name": "Greg Sherwood",
1185 | "role": "lead"
1186 | }
1187 | ],
1188 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
1189 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
1190 | "keywords": [
1191 | "phpcs",
1192 | "standards"
1193 | ],
1194 | "time": "2019-12-04T04:46:47+00:00"
1195 | },
1196 | {
1197 | "name": "symfony/polyfill-ctype",
1198 | "version": "v1.13.1",
1199 | "source": {
1200 | "type": "git",
1201 | "url": "https://github.com/symfony/polyfill-ctype.git",
1202 | "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
1203 | },
1204 | "dist": {
1205 | "type": "zip",
1206 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
1207 | "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
1208 | "shasum": ""
1209 | },
1210 | "require": {
1211 | "php": ">=5.3.3"
1212 | },
1213 | "suggest": {
1214 | "ext-ctype": "For best performance"
1215 | },
1216 | "type": "library",
1217 | "extra": {
1218 | "branch-alias": {
1219 | "dev-master": "1.13-dev"
1220 | }
1221 | },
1222 | "autoload": {
1223 | "psr-4": {
1224 | "Symfony\\Polyfill\\Ctype\\": ""
1225 | },
1226 | "files": [
1227 | "bootstrap.php"
1228 | ]
1229 | },
1230 | "notification-url": "https://packagist.org/downloads/",
1231 | "license": [
1232 | "MIT"
1233 | ],
1234 | "authors": [
1235 | {
1236 | "name": "Gert de Pagter",
1237 | "email": "BackEndTea@gmail.com"
1238 | },
1239 | {
1240 | "name": "Symfony Community",
1241 | "homepage": "https://symfony.com/contributors"
1242 | }
1243 | ],
1244 | "description": "Symfony polyfill for ctype functions",
1245 | "homepage": "https://symfony.com",
1246 | "keywords": [
1247 | "compatibility",
1248 | "ctype",
1249 | "polyfill",
1250 | "portable"
1251 | ],
1252 | "time": "2019-11-27T13:56:44+00:00"
1253 | },
1254 | {
1255 | "name": "symfony/yaml",
1256 | "version": "v3.4.36",
1257 | "source": {
1258 | "type": "git",
1259 | "url": "https://github.com/symfony/yaml.git",
1260 | "reference": "dab657db15207879217fc81df4f875947bf68804"
1261 | },
1262 | "dist": {
1263 | "type": "zip",
1264 | "url": "https://api.github.com/repos/symfony/yaml/zipball/dab657db15207879217fc81df4f875947bf68804",
1265 | "reference": "dab657db15207879217fc81df4f875947bf68804",
1266 | "shasum": ""
1267 | },
1268 | "require": {
1269 | "php": "^5.5.9|>=7.0.8",
1270 | "symfony/polyfill-ctype": "~1.8"
1271 | },
1272 | "conflict": {
1273 | "symfony/console": "<3.4"
1274 | },
1275 | "require-dev": {
1276 | "symfony/console": "~3.4|~4.0"
1277 | },
1278 | "suggest": {
1279 | "symfony/console": "For validating YAML files using the lint command"
1280 | },
1281 | "type": "library",
1282 | "extra": {
1283 | "branch-alias": {
1284 | "dev-master": "3.4-dev"
1285 | }
1286 | },
1287 | "autoload": {
1288 | "psr-4": {
1289 | "Symfony\\Component\\Yaml\\": ""
1290 | },
1291 | "exclude-from-classmap": [
1292 | "/Tests/"
1293 | ]
1294 | },
1295 | "notification-url": "https://packagist.org/downloads/",
1296 | "license": [
1297 | "MIT"
1298 | ],
1299 | "authors": [
1300 | {
1301 | "name": "Fabien Potencier",
1302 | "email": "fabien@symfony.com"
1303 | },
1304 | {
1305 | "name": "Symfony Community",
1306 | "homepage": "https://symfony.com/contributors"
1307 | }
1308 | ],
1309 | "description": "Symfony Yaml Component",
1310 | "homepage": "https://symfony.com",
1311 | "time": "2019-10-24T15:33:53+00:00"
1312 | },
1313 | {
1314 | "name": "webmozart/assert",
1315 | "version": "1.6.0",
1316 | "source": {
1317 | "type": "git",
1318 | "url": "https://github.com/webmozart/assert.git",
1319 | "reference": "573381c0a64f155a0d9a23f4b0c797194805b925"
1320 | },
1321 | "dist": {
1322 | "type": "zip",
1323 | "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925",
1324 | "reference": "573381c0a64f155a0d9a23f4b0c797194805b925",
1325 | "shasum": ""
1326 | },
1327 | "require": {
1328 | "php": "^5.3.3 || ^7.0",
1329 | "symfony/polyfill-ctype": "^1.8"
1330 | },
1331 | "conflict": {
1332 | "vimeo/psalm": "<3.6.0"
1333 | },
1334 | "require-dev": {
1335 | "phpunit/phpunit": "^4.8.36 || ^7.5.13"
1336 | },
1337 | "type": "library",
1338 | "autoload": {
1339 | "psr-4": {
1340 | "Webmozart\\Assert\\": "src/"
1341 | }
1342 | },
1343 | "notification-url": "https://packagist.org/downloads/",
1344 | "license": [
1345 | "MIT"
1346 | ],
1347 | "authors": [
1348 | {
1349 | "name": "Bernhard Schussek",
1350 | "email": "bschussek@gmail.com"
1351 | }
1352 | ],
1353 | "description": "Assertions to validate method input/output with nice error messages.",
1354 | "keywords": [
1355 | "assert",
1356 | "check",
1357 | "validate"
1358 | ],
1359 | "time": "2019-11-24T13:36:37+00:00"
1360 | },
1361 | {
1362 | "name": "wp-coding-standards/wpcs",
1363 | "version": "2.2.0",
1364 | "source": {
1365 | "type": "git",
1366 | "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
1367 | "reference": "f90e8692ce97b693633db7ab20bfa78d930f536a"
1368 | },
1369 | "dist": {
1370 | "type": "zip",
1371 | "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/f90e8692ce97b693633db7ab20bfa78d930f536a",
1372 | "reference": "f90e8692ce97b693633db7ab20bfa78d930f536a",
1373 | "shasum": ""
1374 | },
1375 | "require": {
1376 | "php": ">=5.4",
1377 | "squizlabs/php_codesniffer": "^3.3.1"
1378 | },
1379 | "require-dev": {
1380 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
1381 | "phpcompatibility/php-compatibility": "^9.0",
1382 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
1383 | },
1384 | "suggest": {
1385 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
1386 | },
1387 | "type": "phpcodesniffer-standard",
1388 | "notification-url": "https://packagist.org/downloads/",
1389 | "license": [
1390 | "MIT"
1391 | ],
1392 | "authors": [
1393 | {
1394 | "name": "Contributors",
1395 | "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
1396 | }
1397 | ],
1398 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
1399 | "keywords": [
1400 | "phpcs",
1401 | "standards",
1402 | "wordpress"
1403 | ],
1404 | "time": "2019-11-11T12:34:03+00:00"
1405 | },
1406 | {
1407 | "name": "wpsh/local",
1408 | "version": "0.2.3",
1409 | "source": {
1410 | "type": "git",
1411 | "url": "https://github.com/wpsh/wpsh-local.git",
1412 | "reference": "17338432ed06386273a1ee4db8c8467aa8feab17"
1413 | },
1414 | "dist": {
1415 | "type": "zip",
1416 | "url": "https://api.github.com/repos/wpsh/wpsh-local/zipball/17338432ed06386273a1ee4db8c8467aa8feab17",
1417 | "reference": "17338432ed06386273a1ee4db8c8467aa8feab17",
1418 | "shasum": ""
1419 | },
1420 | "type": "library",
1421 | "notification-url": "https://packagist.org/downloads/",
1422 | "license": [
1423 | "MIT"
1424 | ],
1425 | "description": "A Docker Compose development environment for any project.",
1426 | "time": "2019-06-12T10:54:32+00:00"
1427 | },
1428 | {
1429 | "name": "xwp/wp-dev-lib",
1430 | "version": "1.3.2",
1431 | "source": {
1432 | "type": "git",
1433 | "url": "https://github.com/xwp/wp-dev-lib.git",
1434 | "reference": "31c72369fc43a4c956c43732f669dd3c7e179cef"
1435 | },
1436 | "dist": {
1437 | "type": "zip",
1438 | "url": "https://api.github.com/repos/xwp/wp-dev-lib/zipball/31c72369fc43a4c956c43732f669dd3c7e179cef",
1439 | "reference": "31c72369fc43a4c956c43732f669dd3c7e179cef",
1440 | "shasum": ""
1441 | },
1442 | "type": "library",
1443 | "notification-url": "https://packagist.org/downloads/",
1444 | "license": [
1445 | "MIT"
1446 | ],
1447 | "authors": [
1448 | {
1449 | "name": "Weston Ruter",
1450 | "email": "weston@xwp.co",
1451 | "homepage": "https://weston.ruter.net"
1452 | },
1453 | {
1454 | "name": "XWP",
1455 | "email": "engage@xwp.co",
1456 | "homepage": "https://xwp.co"
1457 | }
1458 | ],
1459 | "description": "Common code used during development of WordPress plugins and themes",
1460 | "homepage": "https://github.com/xwp/wp-dev-lib",
1461 | "keywords": [
1462 | "wordpress"
1463 | ],
1464 | "time": "2020-01-08T06:05:16+00:00"
1465 | }
1466 | ],
1467 | "aliases": [],
1468 | "minimum-stability": "stable",
1469 | "stability-flags": [],
1470 | "prefer-stable": false,
1471 | "prefer-lowest": false,
1472 | "platform": [],
1473 | "platform-dev": [],
1474 | "platform-overrides": {
1475 | "php": "5.6"
1476 | }
1477 | }
1478 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 |
5 | mysql:
6 | image: mysql:5
7 | volumes:
8 | - db_data:/var/lib/mysql
9 | restart: always
10 | environment:
11 | MYSQL_DATABASE: wordpress
12 | MYSQL_USER: wordpress
13 | MYSQL_PASSWORD: password
14 | MYSQL_ROOT_PASSWORD: password
15 |
16 | wordpress:
17 | image: wordpress:php7.3-apache
18 | depends_on:
19 | - mysql
20 | ports:
21 | - "80:80"
22 | - "443:443"
23 | volumes:
24 | - .:/var/www/html/wp-content/plugins/application-passwords
25 | - ./dist:/var/www/html/wp-content/plugins/application-passwords-dist
26 | restart: always
27 | environment:
28 | WORDPRESS_DEBUG: 1
29 | WORDPRESS_DB_USER: wordpress
30 | WORDPRESS_DB_PASSWORD: password
31 |
32 | volumes:
33 | db_data: {}
34 | wp_data: {}
35 |
--------------------------------------------------------------------------------
/gruntfile.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 |
3 | module.exports = function( grunt ) {
4 | 'use strict';
5 |
6 | require( 'load-grunt-tasks' )( grunt );
7 |
8 | grunt.initConfig( {
9 | dist_dir: 'dist',
10 |
11 | clean: {
12 | dist: [ '<%= dist_dir %>' ],
13 | },
14 |
15 | copy: {
16 | dist: {
17 | files: [
18 | // Ensure that all required files are included in the distribution bundle.
19 | {
20 | src: [
21 | '*.php',
22 | 'application-passwords.js',
23 | 'application-passwords.css',
24 | 'auth-app.js',
25 | 'readme.md',
26 | 'readme.txt',
27 | 'composer.json',
28 | 'composer.lock',
29 | ],
30 | dest: '<%= dist_dir %>',
31 | expand: true,
32 | },
33 | ],
34 | },
35 | },
36 |
37 | wp_deploy: {
38 | options: {
39 | plugin_slug: 'application-passwords',
40 | build_dir: '<%= dist_dir %>',
41 | assets_dir: 'assets',
42 | },
43 | trunk: {
44 | options: {
45 | deploy_tag: false,
46 | deploy_trunk: true,
47 | },
48 | },
49 | },
50 | } );
51 |
52 | grunt.registerTask(
53 | 'build', [
54 | 'clean',
55 | 'copy',
56 | ]
57 | );
58 |
59 | grunt.registerTask(
60 | 'deploy', [
61 | 'build',
62 | 'wp_deploy:trunk',
63 | ]
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@georgestephanis/application-passwords",
3 | "version": "1.0.0",
4 | "description": "Provide Application Passwords for WordPress core",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/WordPress/application-passwords.git"
8 | },
9 | "author": "George Stephanis (https://stephanis.info)",
10 | "license": "GPL-2.0-or-later",
11 | "devDependencies": {
12 | "@wordpress/eslint-plugin": "^2.4.0",
13 | "eslint": "^6.8.0",
14 | "grunt": "^1.3.0",
15 | "grunt-cli": "^1.3.2",
16 | "grunt-contrib-clean": "^2.0.0",
17 | "grunt-contrib-copy": "^1.0.0",
18 | "grunt-wp-deploy": "^2.1.2",
19 | "husky": "^3.1.0",
20 | "load-grunt-tasks": "^5.1.0"
21 | },
22 | "scripts": {
23 | "postinstall": "composer install",
24 | "lint-js": "eslint .",
25 | "lint-staged": "npm run lint-js && composer lint-staged",
26 | "lint": "npm run lint-js && composer lint-head",
27 | "build": "grunt build",
28 | "deploy": "grunt deploy"
29 | },
30 | "husky": {
31 | "hooks": {
32 | "pre-commit": "npm run lint-staged"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | Generally-applicable sniffs for WordPress plugins
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | */node_modules/*
15 | */vendor/*
16 |
17 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 | tests
15 |
16 |
17 |
18 |
19 | .
20 |
21 | tests
22 | vendor
23 | node_modules
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Application Passwords for WordPress
2 |
3 | **⚠️ IMPORTANT: This plugin has been merged into WordPress core version 5.6 and doesn't have to be installed separately. [View the integration guide →](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/)**
4 |
5 | If you wish to open a ticket relating to this feature, please do so in the [Application Passwords component](https://make.wordpress.org/core/components/rest-api/application-passwords/) on the WordPress Core issue tracker.
6 |
7 | ---
8 |
9 | Creates unique passwords for applications to authenticate users without revealing their main passwords.
10 |
11 |
12 | ## Install
13 |
14 | This plugin has been merged into WordPress core version 5.6 and doesn't have to be installed separately. [View the integration guide →](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/)
15 |
16 | ## Credits
17 |
18 | Created by [George Stephanis](https://github.com/georgestephanis). View [all contributors](https://github.com/WordPress/application-passwords/graphs/contributors).
19 |
--------------------------------------------------------------------------------
/readme.txt:
--------------------------------------------------------------------------------
1 | === Application Passwords ===
2 | Contributors: georgestephanis, valendesigns, kraftbj, kasparsd, passoniate
3 | Tags: application-passwords, rest api, xml-rpc, security, authentication
4 | Requires at least: 4.4
5 | Tested up to: 5.5
6 | Stable tag: trunk
7 | License: GPLv2 or later
8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html
9 |
10 | Creates unique passwords for applications to authenticate users without revealing their main passwords.
11 |
12 |
13 | == Description ==
14 |
15 | **⚠️ IMPORTANT: This plugin has been merged into WordPress core version 5.6 and doesn't have to be installed separately. [View the integration guide →](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/)**
16 |
17 | ---
18 |
19 | Use Application Passwords to authenticate users without providing their passwords directly. Instead, a unique password is generated for each application without revealing the user's main password. Application passwords can be revoked for each application individually.
20 |
21 | **Important:** Application Passwords can be used only for authenticating API requests such as [REST API](https://developer.wordpress.org/rest-api/) and [XML-RPC](https://codex.wordpress.org/XML-RPC_WordPress_API), and they won't work for regular site logins.
22 |
23 |
24 | = Contribute =
25 |
26 | - Translate the plugin [into your language](https://translate.wordpress.org/projects/wp-plugins/application-passwords/).
27 | - Report issues, suggest features and contribute code [on GitHub](https://github.com/WordPress/application-passwords).
28 |
29 |
30 | = Creating Application Password Manually =
31 |
32 | 1. Go the User Profile page of the user that you want to generate a new application password for. To do so, click *Users* on the left side of the WordPress admin, then click on the user that you want to manage.
33 | 2. Scroll down until you see the Application Passwords section. This is typically at the bottom of the page.
34 | 3. Within the input field, type in a name for your new application password, then click *Add New*.
35 | **Note:** The application password name is only used to describe your password for easy management later. It will not affect your password in any way. Be descriptive, as it will lead to easier management if you ever need to change it later.
36 | 4. Once the *Add New* button is clicked, your new application password will appear. Be sure to keep this somewhere safe, as it will not be displayed to you again. If you lose this password, it cannot be obtained again.
37 |
38 |
39 | = Two Factor Support =
40 |
41 | Application Passwords can be used together with the [Two Factor plugin](https://wordpress.org/plugins/two-factor/) as long as you bypass the API acccess restrictions added by the Two Factor plugin. Those protections disable API requests with password authentication _for users with Two Factor enabled_.
42 |
43 | Use the `two_factor_user_api_login_enable` filter to allow API requests authenticated using an application passwords:
44 |
45 | add_filter( 'two_factor_user_api_login_enable', function( $enable ) {
46 | // Allow API login when using an application password even with 2fa enabled.
47 | if ( did_action( 'application_password_did_authenticate' ) ) {
48 | return true;
49 | }
50 |
51 | return $enable;
52 | } );
53 |
54 | This is not required if the user associated with the application password doesn't have any of the Two Factor methods enabled.
55 |
56 |
57 | = Requesting Password for Application =
58 |
59 | To request a password for your application, redirect users to:
60 |
61 | https://example.com/wp-admin/admin.php?page=auth_app
62 |
63 | and use the following `GET` request parameters to specify:
64 |
65 | - `app_name` (required) - The human readable identifier for your app. This will be the name of the generated application password, so structure it like ... "WordPress Mobile App on iPhone 12" for uniqueness between multiple versions. If omitted, the user will be required to provide an application name.
66 | - `success_url` (recommended) - The URL that you'd like the user to be sent to if they approve the connection. Two GET variables will be appended when they are passed back -- `user_login` and `password` -- these credentials can then be used for API calls. If the `success_url` variable is omitted, a password will be generated and displayed to the user, to manually enter into your application.
67 | - `reject_url` (optional) - If included, the user will get sent there if they reject the connection. If omitted, the user will be sent to the `success_url`, with `?success=false` appended to the end. If the `success_url` is omitted, the user will be sent to their dashboard.
68 |
69 |
70 | = Testing an Application Password =
71 |
72 | We use [curl](https://curl.haxx.se) to send HTTP requests to the API endpoints in the examples below.
73 |
74 | #### WordPress REST API
75 |
76 | Make a REST API call to update a post. Because you are performing a `POST` request, you will need to authorize the request using your newly created base64 encoded access token. If authorized correctly, you will see the post title update to "New Title."
77 |
78 | curl --user "USERNAME:APPLICATION_PASSWORD" -X POST -d "title=New Title" https://LOCALHOST/wp-json/wp/v2/posts/POST_ID
79 |
80 | When running this command, be sure to replace `USERNAME` and `APPLICATION_PASSWORD` with your credentials (curl takes care of base64 encoding and setting the `Authorization` header), `LOCALHOST` with the hostname of your WordPress installation, and `POST_ID` with the ID of the post that you want to edit.
81 |
82 | #### XML-RPC
83 |
84 | Unlike the WordPress REST API, XML-RPC does not require your username and password to be base64 encoded. Send an XML-RPC request to list all users:
85 |
86 | curl -H 'Content-Type: text/xml' -d 'wp.getUsers1USERNAMEPASSWORD' https://LOCALHOST/xmlrpc.php
87 |
88 | In the above example, replace `USERNAME` with your username, `PASSWORD` with your new application password, and `LOCALHOST` with the hostname of your WordPress installation. This should output a response containing all users on your site.
89 |
90 |
91 | = Plugin History =
92 |
93 | This is a feature plugin that is a spinoff of the main [Two-Factor Authentication plugin](https://github.com/WordPress/two-factor/).
94 |
95 |
96 | == Changelog ==
97 |
98 | See the [release notes on GitHub](https://github.com/WordPress/application-passwords/releases).
99 |
100 |
101 | == Installation ==
102 |
103 | Search for "Application Passwords" under "Plugins" → "Add New" in your WordPress dashboard to install the plugin.
104 |
105 | Or install it manually:
106 |
107 | 1. Download the [plugin zip file](https://downloads.wordpress.org/plugin/application-passwords.zip).
108 | 2. Go to *Plugins* → *Add New* in your WordPress admin.
109 | 3. Click on the *Upload Plugin* button.
110 | 4. Select the file you downloaded.
111 | 5. Click *Install Plugin*.
112 | 6. Activate.
113 |
114 | = Using Composer =
115 |
116 | Add this plugin as a [Composer](https://getcomposer.org) dependency [from Packagist](https://packagist.org/packages/georgestephanis/application-passwords):
117 |
118 | composer require georgestephanis/application-passwords
119 |
120 |
121 | == Screenshots ==
122 |
123 | 1. New application passwords has been created.
124 | 2. After at least one Application Password for you account exists, you'll see a table displaying them, allowing you to view usage and revoke them as desired.
125 |
--------------------------------------------------------------------------------
/tests/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/test-class.application-passwords.php:
--------------------------------------------------------------------------------
1 | assertEquals( 10, has_action( 'authenticate', array( 'Application_Passwords', 'authenticate' ) ) );
16 | }
17 |
18 | /**
19 | * Regular front-end requests are not REST requests.
20 | *
21 | * @covers Application_Passwords::is_api_request()
22 | */
23 | public function test_rest_api_request() {
24 | $this->assertFalse( Application_Passwords::is_api_request() );
25 | }
26 |
27 | /**
28 | * HTTP Auth headers are used to determine the current user.
29 | *
30 | * @covers Application_Passwords::rest_api_auth_handler()
31 | */
32 | public function test_can_login_user_through_http_auth_headers() {
33 | $user_id = $this->factory->user->create(
34 | array(
35 | 'user_login' => 'http_auth_login',
36 | 'user_pass' => 'http_auth_pass', // Shouldn't be allowed for API login.
37 | )
38 | );
39 |
40 | // Create a new app-only password.
41 | $user_app_password = Application_Passwords::create_new_application_password( $user_id, 'phpunit' );
42 |
43 | // Fake a REST API request.
44 | add_filter( 'application_password_is_api_request', '__return_true' );
45 |
46 | // Fake an HTTP Auth request with the regular account password first.
47 | $_SERVER['PHP_AUTH_USER'] = 'http_auth_login';
48 | $_SERVER['PHP_AUTH_PW'] = 'http_auth_pass';
49 |
50 | $this->assertEquals(
51 | 0,
52 | Application_Passwords::rest_api_auth_handler( null ),
53 | 'Regular user account password should not be allowed for API authenticaiton'
54 | );
55 |
56 | // Not try with an App password instead.
57 | $_SERVER['PHP_AUTH_PW'] = $user_app_password[0];
58 |
59 | $this->assertEquals(
60 | $user_id,
61 | Application_Passwords::rest_api_auth_handler( null ),
62 | 'Application passwords should be allowed for API authentication'
63 | );
64 |
65 | remove_filter( 'application_password_is_api_request', '__return_true' );
66 |
67 | // Cleanup all the global state.
68 | unset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------