-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a SVN Password functionality #280
Changes from all commits
902f846
414dbe4
f2135a6
4d9ecf7
e5c853c
e26e188
6c22e27
20a862a
b441556
fdd7689
9b27770
a1ea2a2
b3e42cd
fb5de98
c19e325
762a291
f9dc398
c070587
805e8ba
21b873e
f757d2d
98fcc52
412ebf5
5041a59
4f358fc
ec1a77d
41c06dd
9cc14d2
c03940b
744e047
c283b82
e94d95e
1509ca9
748ed28
55727ac
b923ff5
668f0dd
1542311
56663ac
5305a81
e37ac17
ad8dfc1
82919f3
8d9d684
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
use Two_Factor_Core, Two_Factor_Totp, Two_Factor_Backup_Codes; | ||
use WildWolf\WordPress\TwoFactorWebAuthn\{ WebAuthn_Credential_Store }; | ||
use WP_REST_Server, WP_REST_Request, WP_Error, WP_User; | ||
use function WordPressdotorg\Security\SVNPasswords\{ set_svn_password, get_svn_password_creation_date }; | ||
|
||
defined( 'WPINC' ) || die(); | ||
|
||
|
@@ -131,6 +132,39 @@ function register_rest_routes() : void { | |
), | ||
), | ||
); | ||
|
||
register_rest_route( | ||
'wporg-two-factor/1.0', | ||
'/generate-svn-password', | ||
array( | ||
'methods' => WP_REST_Server::EDITABLE, | ||
'callback' => function( $request ) { | ||
$user = get_userdata( $request['user_id'] ); | ||
|
||
// Local environment doesn't have the SVN password system, just mock it. | ||
if ( ! function_exists( 'WordPressdotorg\Security\SVNPasswords\set_svn_password' ) ) { | ||
return 'Local Development: SVN Password system unavailable.'; | ||
} | ||
|
||
return [ | ||
'svn_password' => set_svn_password( $user->ID ) | ||
]; | ||
}, | ||
'permission_callback' => function( $request ) { | ||
return Two_Factor_Core::rest_api_can_edit_user_and_update_two_factor_options( $request['user_id'] ); | ||
}, | ||
'args' => array( | ||
'user_id' => array( | ||
'required' => true, | ||
'type' => 'number', | ||
'sanitize_callback' => 'absint', | ||
'validate_callback' => function( $user_id ) { | ||
return get_userdata( $user_id ) instanceof WP_User; | ||
}, | ||
), | ||
Comment on lines
+157
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we allow user impersonation in WP.org? If not, maybe we can just use the current user id? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do, although not how you may have invisioned it here. Currently a super-admin can modify another users account data, that's why I'm not a super-fan of it, but it's how the other endpoints in the plugin operate, so it made sense to duplicate that here. An example of where this IS needed, is a case of accounts that cannot be logged into, system-level automation accounts that we need a SVN password for. However, we do have the CLI commands that can be used for that. It also allows for super-admins to be able to quickly re-generate a new svn password for a user to lock them out (although, that's not something we'd likely make use of). Happy to change this to throw a error along the lines of |
||
), | ||
), | ||
); | ||
} | ||
|
||
/** | ||
|
@@ -323,6 +357,61 @@ function register_user_fields(): void { | |
] | ||
); | ||
|
||
register_rest_field( | ||
'user', | ||
'svn_password_required', | ||
[ | ||
'get_callback' => function( $user ) { | ||
global $wpdb; | ||
|
||
$user = get_userdata( $user['id'] ); | ||
if ( ! $user ) { | ||
return false; | ||
} | ||
|
||
// Committers, supes, etc. It's likely these users will need a SVN password. | ||
if ( function_exists( 'is_special_user' ) && is_special_user( $user->ID ) ) { | ||
return true; | ||
} | ||
|
||
// Plugin committers & Theme authors have this user meta set. | ||
if ( $user->has_plugins || $user->has_themes ) { | ||
return true; | ||
} | ||
|
||
return false; | ||
}, | ||
'schema' => [ | ||
'type' => 'boolean', | ||
'context' => [ 'edit' ], | ||
] | ||
] | ||
); | ||
dd32 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
register_rest_field( | ||
'user', | ||
'svn_password_created', | ||
[ | ||
'get_callback' => function( $user ) { | ||
// Local environment doesn't have the SVN password system, just return false for that. | ||
if ( ! function_exists( 'WordPressdotorg\Security\SVNPasswords\get_svn_password_creation_date' ) ) { | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this returns There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, but not really at the same time. You can return Dummy data here, but if you hard-code dummy data you can't then test the branch where you've not set a password. The best option would probably be to add a local test variant of the svn password functionality that uses user meta. |
||
} | ||
|
||
$svn_password_created_date = get_svn_password_creation_date( $user['id'] ); | ||
if ( ! $svn_password_created_date ) { | ||
return false; | ||
} | ||
|
||
return $svn_password_created_date; | ||
}, | ||
'schema' => [ | ||
'type' => [ 'boolean', 'string' ], | ||
'context' => [ 'edit' ], | ||
] | ||
] | ||
); | ||
|
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import apiFetch from '@wordpress/api-fetch'; | ||
import { Button } from '@wordpress/components'; | ||
import { useCallback, useContext, useMemo, useState } from '@wordpress/element'; | ||
import { refreshRecord } from '../utilities/common'; | ||
import CopyToClipboardButton from './copy-to-clipboard-button'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { GlobalContext } from '../script'; | ||
|
||
/** | ||
* Render the Email setting. | ||
*/ | ||
export default function SVNPassword() { | ||
const { | ||
user: { userRecord }, | ||
setError, | ||
} = useContext( GlobalContext ); | ||
|
||
const [ isGenerating, setGenerating ] = useState( false ); | ||
const [ generatedPassword, setGeneratedPassword ] = useState( '' ); | ||
|
||
// Generate a new SVN Password. | ||
const handleGenerate = useCallback( async () => { | ||
try { | ||
setGenerating( true ); | ||
|
||
const response = await apiFetch( { | ||
path: '/wporg-two-factor/1.0/generate-svn-password', | ||
method: 'POST', | ||
data: { | ||
user_id: userRecord.record.id, | ||
}, | ||
} ); | ||
|
||
// Fill in the creation date in the user record, we'll refresh it for the actual data below. | ||
userRecord.record.svn_password_created = new Date().toISOString(); | ||
|
||
setGeneratedPassword( response.svn_password ); | ||
setGenerating( false ); | ||
|
||
await refreshRecord( userRecord ); | ||
} catch ( apiFetchError ) { | ||
setError( apiFetchError ); | ||
} | ||
} ); | ||
|
||
const getButtonText = useMemo( () => { | ||
if ( isGenerating ) { | ||
return 'Generating...'; | ||
} | ||
|
||
if ( ! userRecord.record.svn_password_created ) { | ||
return 'Generate Password'; | ||
} | ||
|
||
return 'Regenerate Password'; | ||
}, [ isGenerating, userRecord.record.svn_password_created ] ); | ||
|
||
return ( | ||
<> | ||
<p> | ||
WordPress.org uses Subversion (SVN) for version control, providing each hosted | ||
plugin and theme with a repository that the author can commit to. For information on | ||
using SVN, please see the{ ' ' } | ||
<a href="https://developer.wordpress.org/plugins/wordpress-org/how-to-use-subversion/"> | ||
WordPress.org Plugin Developer Handbook | ||
</a> | ||
. | ||
</p> | ||
|
||
<p className="wporg-2fa__screen-intro"> | ||
For security, your WordPress.org account password should not be used to commit to | ||
SVN, use a separate SVN password, which you can generate here. | ||
</p> | ||
|
||
<h4>Details</h4> | ||
<ul> | ||
<li> | ||
Username: <code>{ userRecord.record.username }</code>{ ' ' } | ||
{ userRecord.record.username.match( /[^a-z0-9]/ ) && <>(case-sensitive)</> } | ||
</li> | ||
<li> | ||
Password:{ ' ' } | ||
{ generatedPassword || userRecord.record.svn_password_created ? ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically you're probably right, it might not be required, since we set In practise, I found it to be required, as without it it caused the UI to "flash" as it re-rendered the branch. |
||
<> | ||
<code>{ generatedPassword || 'svn_*****************' }</code> | ||
| ||
{ generatedPassword && ( | ||
<CopyToClipboardButton | ||
variant="link" | ||
contents={ generatedPassword } | ||
/> | ||
) } | ||
{ userRecord.record.svn_password_created && ( | ||
<div className="wporg-2fa__svn-password_generated"> | ||
Generated on{ ' ' } | ||
{ new Date( | ||
userRecord.record.svn_password_created | ||
).toLocaleDateString() } | ||
</div> | ||
) } | ||
</> | ||
) : ( | ||
<> | ||
<em>Not configured</em> | ||
</> | ||
) } | ||
</li> | ||
</ul> | ||
<div className="wporg-2fa__submit-actions"> | ||
<Button | ||
variant="secondary" | ||
onClick={ handleGenerate } | ||
isBusy={ isGenerating } | ||
disabled={ isGenerating } | ||
> | ||
{ getButtonText } | ||
</Button> | ||
</div> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
.wporg-2fa__svn-password { | ||
code { | ||
display: inline-block; | ||
padding: 0 3px; | ||
background: var(--wp--preset--color--light-grey-2, #f6f6f6); | ||
border-radius: 2px; | ||
} | ||
|
||
ul { | ||
padding-top: 16px; | ||
|
||
li:not(:last-child) { | ||
margin-bottom: 8px; | ||
|
||
.wporg-2fa__svn-password_generated { | ||
color: var(--wp--preset--color--charcoal-4, #656a71); | ||
} | ||
} | ||
} | ||
|
||
.wporg-2fa__svn-password_generated { | ||
padding-top: 4px; | ||
font-size: 12px; | ||
color: var(--wp--preset--color--charcoal-4, #656a71); | ||
} | ||
} | ||
dd32 marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we restrict this endpoint to users only need a svn password? It seems like any authenticated .org user will be able to set their own SVN password here regardless if they need it or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was intentional, as WordPress is not aware of every user that may need a SVN password, such as those who are granted directly via svn configuration files (ie. develop.svn.wordpress.org).
Leaving this open allows for direct links to the svn password screen to be used, rather than having to set some user-meta to allow access.