Some security against XSRF-attacks

This commit is contained in:
Tobias Hößl 2012-03-12 20:17:37 +00:00
parent 9574f7df03
commit 59766b944c
13 changed files with 131 additions and 38 deletions

View file

@ -288,3 +288,49 @@ function item_permissions_sql($owner_id,$remote_verified = false,$groups = null)
} }
/*
* Functions used to protect against Cross-Site Request Forgery
* The security token has to base on at least one value that an attacker can't know - here it's the session ID and the private key.
* In this implementation, a security token is reusable (if the user submits a form, goes back and resubmits the form, maybe with small changes;
* or if the security token is used for ajax-calls that happen several times), but only valid for a certain amout of time (3hours).
* The "typename" seperates the security tokens of different types of forms. This could be relevant in the following case:
* A security token is used to protekt a link from CSRF (e.g. the "delete this profile"-link).
* If the new page contains by any chance external elements, then the used security token is exposed by the referrer.
* Actually, important actions should not be triggered by Links / GET-Requests at all, but somethimes they still are,
* so this mechanism brings in some damage control (the attacker would be able to forge a request to a form of this type, but not to forms of other types).
*/
function get_form_security_token($typename = "") {
$a = get_app();
$timestamp = time();
$sec_hash = hash('whirlpool', $a->user["guid"] . $a->user["prvkey"] . session_id() . $timestamp . $typename);
return $timestamp . "." . $sec_hash;
}
function check_form_security_token($typename = "", $formname = 'form_security_token') {
if (!x($_REQUEST, $formname)) return false;
$hash = $_REQUEST[$formname];
$max_livetime = 10800; // 3 hours
$a = get_app();
$x = explode(".", $hash);
if (time() > (IntVal($x[0]) + $max_livetime)) return false;
$sec_hash = hash('whirlpool', $a->user["guid"] . $a->user["prvkey"] . session_id() . $x[0] . $typename);
return ($sec_hash == $x[1]);
}
function check_form_security_std_err_msg() {
return t('The form security token was not correct. This probably happened because the form has been opened for too long (>3 hours) before subitting it.') . EOL;
}
function check_form_security_token_redirectOnErr($err_redirect, $typename = "", $formname = 'form_security_token') {
if (!check_form_security_token($typename, $formname)) {
$a = get_app();
notice( check_form_security_std_err_msg() );
goaway($a->get_baseurl() . $err_redirect );
}
}

View file

@ -15,10 +15,12 @@ function profile_photo_init(&$a) {
function profile_photo_post(&$a) { function profile_photo_post(&$a) {
if(! local_user()) { if(! local_user()) {
notice ( t('Permission denied.') . EOL ); notice ( t('Permission denied.') . EOL );
return; return;
} }
check_form_security_token_redirectOnErr('/profile_photo', 'profile_photo');
if((x($_POST,'cropfinal')) && ($_POST['cropfinal'] == 1)) { if((x($_POST,'cropfinal')) && ($_POST['cropfinal'] == 1)) {
@ -149,6 +151,8 @@ function profile_photo_content(&$a) {
return; return;
}; };
check_form_security_token_redirectOnErr('/profile_photo', 'profile_photo');
$resource_id = $a->argv[2]; $resource_id = $a->argv[2];
//die(":".local_user()); //die(":".local_user());
$r=q("SELECT * FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' ORDER BY `scale` ASC", $r=q("SELECT * FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' ORDER BY `scale` ASC",
@ -203,6 +207,7 @@ function profile_photo_content(&$a) {
'$lbl_upfile' => t('Upload File:'), '$lbl_upfile' => t('Upload File:'),
'$title' => t('Upload Profile Photo'), '$title' => t('Upload Profile Photo'),
'$submit' => t('Upload'), '$submit' => t('Upload'),
'$form_security_token' => get_form_security_token("profile_photo"),
'$select' => sprintf('%s %s', t('or'), ($newuser) ? '<a href="' . $a->get_baseurl() . '">' . t('skip this step') . '</a>' : '<a href="'. $a->get_baseurl() . '/photos/' . $a->user['nickname'] . '">' . t('select a photo from your photo albums') . '</a>') '$select' => sprintf('%s %s', t('or'), ($newuser) ? '<a href="' . $a->get_baseurl() . '">' . t('skip this step') . '</a>' : '<a href="'. $a->get_baseurl() . '/photos/' . $a->user['nickname'] . '">' . t('select a photo from your photo albums') . '</a>')
)); ));
@ -218,6 +223,7 @@ function profile_photo_content(&$a) {
'$image_url' => $a->get_baseurl() . '/photo/' . $filename, '$image_url' => $a->get_baseurl() . '/photo/' . $filename,
'$title' => t('Crop Image'), '$title' => t('Crop Image'),
'$desc' => t('Please adjust the image cropping for optimum viewing.'), '$desc' => t('Please adjust the image cropping for optimum viewing.'),
'$form_security_token' => get_form_security_token("profile_photo"),
'$done' => t('Done Editing') '$done' => t('Done Editing')
)); ));
return $o; return $o;

View file

@ -21,6 +21,9 @@ function profiles_post(&$a) {
notice( t('Profile not found.') . EOL); notice( t('Profile not found.') . EOL);
return; return;
} }
check_form_security_token_redirectOnErr('/profiles', 'profile_edit');
$is_default = (($orig[0]['is-default']) ? 1 : 0); $is_default = (($orig[0]['is-default']) ? 1 : 0);
$profile_name = notags(trim($_POST['profile_name'])); $profile_name = notags(trim($_POST['profile_name']));
@ -241,6 +244,8 @@ function profiles_content(&$a) {
return; // NOTREACHED return; // NOTREACHED
} }
check_form_security_token_redirectOnErr('/profiles', 'profile_drop', 't');
// move every contact using this profile as their default to the user default // move every contact using this profile as their default to the user default
$r = q("UPDATE `contact` SET `profile-id` = (SELECT `profile`.`id` AS `profile-id` FROM `profile` WHERE `profile`.`is-default` = 1 AND `profile`.`uid` = %d LIMIT 1) WHERE `profile-id` = %d AND `uid` = %d ", $r = q("UPDATE `contact` SET `profile-id` = (SELECT `profile`.`id` AS `profile-id` FROM `profile` WHERE `profile`.`is-default` = 1 AND `profile`.`uid` = %d LIMIT 1) WHERE `profile-id` = %d AND `uid` = %d ",
@ -265,6 +270,8 @@ function profiles_content(&$a) {
if(($a->argc > 1) && ($a->argv[1] === 'new')) { if(($a->argc > 1) && ($a->argv[1] === 'new')) {
check_form_security_token_redirectOnErr('/profiles', 'profile_new', 't');
$r0 = q("SELECT `id` FROM `profile` WHERE `uid` = %d", $r0 = q("SELECT `id` FROM `profile` WHERE `uid` = %d",
intval(local_user())); intval(local_user()));
$num_profiles = count($r0); $num_profiles = count($r0);
@ -291,11 +298,14 @@ function profiles_content(&$a) {
info( t('New profile created.') . EOL); info( t('New profile created.') . EOL);
if(count($r3) == 1) if(count($r3) == 1)
goaway($a->get_baseurl() . '/profiles/' . $r3[0]['id']); goaway($a->get_baseurl() . '/profiles/' . $r3[0]['id']);
goaway($a->get_baseurl() . '/profiles'); goaway($a->get_baseurl() . '/profiles');
} }
if(($a->argc > 2) && ($a->argv[1] === 'clone')) { if(($a->argc > 2) && ($a->argv[1] === 'clone')) {
check_form_security_token_redirectOnErr('/profiles', 'profile_clone', 't');
$r0 = q("SELECT `id` FROM `profile` WHERE `uid` = %d", $r0 = q("SELECT `id` FROM `profile` WHERE `uid` = %d",
intval(local_user())); intval(local_user()));
$num_profiles = count($r0); $num_profiles = count($r0);
@ -330,8 +340,10 @@ function profiles_content(&$a) {
info( t('New profile created.') . EOL); info( t('New profile created.') . EOL);
if(count($r3) == 1) if(count($r3) == 1)
goaway($a->get_baseurl() . '/profiles/' . $r3[0]['id']); goaway($a->get_baseurl() . '/profiles/' . $r3[0]['id']);
goaway($a->get_baseurl() . '/profiles');
return; // NOTREACHED goaway($a->get_baseurl() . '/profiles');
return; // NOTREACHED
} }
@ -371,6 +383,9 @@ function profiles_content(&$a) {
$is_default = (($r[0]['is-default']) ? 1 : 0); $is_default = (($r[0]['is-default']) ? 1 : 0);
$tpl = get_markup_template("profile_edit.tpl"); $tpl = get_markup_template("profile_edit.tpl");
$o .= replace_macros($tpl,array( $o .= replace_macros($tpl,array(
'$form_security_token' => get_form_security_token("profile_edit"),
'$profile_clone_link' => 'profiles/clone/' . $r[0]['id'] . '?t=' . get_form_security_token("profile_clone"),
'$profile_drop_link' => 'profiles/drop/' . $r[0]['id'] . '?t=' . get_form_security_token("profile_drop"),
'$banner' => t('Edit Profile Details'), '$banner' => t('Edit Profile Details'),
'$submit' => t('Submit'), '$submit' => t('Submit'),
'$viewprof' => t('View this profile'), '$viewprof' => t('View this profile'),
@ -460,7 +475,8 @@ function profiles_content(&$a) {
$o .= replace_macros($tpl_header,array( $o .= replace_macros($tpl_header,array(
'$header' => t('Edit/Manage Profiles'), '$header' => t('Edit/Manage Profiles'),
'$chg_photo' => t('Change profile photo'), '$chg_photo' => t('Change profile photo'),
'$cr_new' => t('Create New Profile') '$cr_new' => t('Create New Profile'),
'$cr_new_link' => 'profiles/new?t=' . get_form_security_token("profile_new")
)); ));

View file

@ -53,6 +53,8 @@ function settings_post(&$a) {
$old_page_flags = $a->user['page-flags']; $old_page_flags = $a->user['page-flags'];
if(($a->argc > 1) && ($a->argv[1] === 'oauth') && x($_POST,'remove')){ if(($a->argc > 1) && ($a->argv[1] === 'oauth') && x($_POST,'remove')){
check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth');
$key = $_POST['remove']; $key = $_POST['remove'];
q("DELETE FROM tokens WHERE id='%s' AND uid=%d", q("DELETE FROM tokens WHERE id='%s' AND uid=%d",
dbesc($key), dbesc($key),
@ -63,6 +65,8 @@ function settings_post(&$a) {
if(($a->argc > 2) && ($a->argv[1] === 'oauth') && ($a->argv[2] === 'edit'||($a->argv[2] === 'add')) && x($_POST,'submit')) { if(($a->argc > 2) && ($a->argv[1] === 'oauth') && ($a->argv[2] === 'edit'||($a->argv[2] === 'add')) && x($_POST,'submit')) {
check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth');
$name = ((x($_POST,'name')) ? $_POST['name'] : ''); $name = ((x($_POST,'name')) ? $_POST['name'] : '');
$key = ((x($_POST,'key')) ? $_POST['key'] : ''); $key = ((x($_POST,'key')) ? $_POST['key'] : '');
$secret = ((x($_POST,'secret')) ? $_POST['secret'] : ''); $secret = ((x($_POST,'secret')) ? $_POST['secret'] : '');
@ -105,13 +109,18 @@ function settings_post(&$a) {
} }
if(($a->argc > 1) && ($a->argv[1] == 'addon')) { if(($a->argc > 1) && ($a->argv[1] == 'addon')) {
check_form_security_token_redirectOnErr('/settings/addon', 'settings_addon');
call_hooks('plugin_settings_post', $_POST); call_hooks('plugin_settings_post', $_POST);
return; return;
} }
if(($a->argc > 1) && ($a->argv[1] == 'connectors')) { if(($a->argc > 1) && ($a->argv[1] == 'connectors')) {
if(x($_POST['imap-submit'])) { check_form_security_token_redirectOnErr('/settings/connectors', 'settings_connectors');
if(x($_POST, 'imap-submit')) {
$mail_server = ((x($_POST,'mail_server')) ? $_POST['mail_server'] : ''); $mail_server = ((x($_POST,'mail_server')) ? $_POST['mail_server'] : '');
$mail_port = ((x($_POST,'mail_port')) ? $_POST['mail_port'] : ''); $mail_port = ((x($_POST,'mail_port')) ? $_POST['mail_port'] : '');
$mail_ssl = ((x($_POST,'mail_ssl')) ? strtolower(trim($_POST['mail_ssl'])) : ''); $mail_ssl = ((x($_POST,'mail_ssl')) ? strtolower(trim($_POST['mail_ssl'])) : '');
@ -185,6 +194,7 @@ function settings_post(&$a) {
return; return;
} }
check_form_security_token_redirectOnErr('/settings', 'settings');
call_hooks('settings_post', $_POST); call_hooks('settings_post', $_POST);
@ -460,6 +470,7 @@ function settings_content(&$a) {
if(($a->argc > 2) && ($a->argv[2] === 'add')) { if(($a->argc > 2) && ($a->argv[2] === 'add')) {
$tpl = get_markup_template("settings_oauth_edit.tpl"); $tpl = get_markup_template("settings_oauth_edit.tpl");
$o .= replace_macros($tpl, array( $o .= replace_macros($tpl, array(
'$form_security_token' => get_form_security_token("settings_oauth"),
'$tabs' => $tabs, '$tabs' => $tabs,
'$title' => t('Add application'), '$title' => t('Add application'),
'$submit' => t('Submit'), '$submit' => t('Submit'),
@ -486,6 +497,7 @@ function settings_content(&$a) {
$tpl = get_markup_template("settings_oauth_edit.tpl"); $tpl = get_markup_template("settings_oauth_edit.tpl");
$o .= replace_macros($tpl, array( $o .= replace_macros($tpl, array(
'$form_security_token' => get_form_security_token("settings_oauth"),
'$tabs' => $tabs, '$tabs' => $tabs,
'$title' => t('Add application'), '$title' => t('Add application'),
'$submit' => t('Update'), '$submit' => t('Update'),
@ -500,6 +512,8 @@ function settings_content(&$a) {
} }
if(($a->argc > 3) && ($a->argv[2] === 'delete')) { if(($a->argc > 3) && ($a->argv[2] === 'delete')) {
check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth', 't');
$r = q("DELETE FROM clients WHERE client_id='%s' AND uid=%d", $r = q("DELETE FROM clients WHERE client_id='%s' AND uid=%d",
dbesc($a->argv[3]), dbesc($a->argv[3]),
local_user()); local_user());
@ -518,6 +532,7 @@ function settings_content(&$a) {
$tpl = get_markup_template("settings_oauth.tpl"); $tpl = get_markup_template("settings_oauth.tpl");
$o .= replace_macros($tpl, array( $o .= replace_macros($tpl, array(
'$form_security_token' => get_form_security_token("settings_oauth"),
'$baseurl' => $a->get_baseurl(), '$baseurl' => $a->get_baseurl(),
'$title' => t('Connected Apps'), '$title' => t('Connected Apps'),
'$add' => t('Add application'), '$add' => t('Add application'),
@ -544,6 +559,7 @@ function settings_content(&$a) {
$tpl = get_markup_template("settings_addons.tpl"); $tpl = get_markup_template("settings_addons.tpl");
$o .= replace_macros($tpl, array( $o .= replace_macros($tpl, array(
'$form_security_token' => get_form_security_token("settings_addons"),
'$title' => t('Plugin Settings'), '$title' => t('Plugin Settings'),
'$tabs' => $tabs, '$tabs' => $tabs,
'$settings_addons' => $settings_addons '$settings_addons' => $settings_addons
@ -586,28 +602,28 @@ function settings_content(&$a) {
$tpl = get_markup_template("settings_connectors.tpl"); $tpl = get_markup_template("settings_connectors.tpl");
$o .= replace_macros($tpl, array( $o .= replace_macros($tpl, array(
'$form_security_token' => get_form_security_token("settings_connectors"),
'$title' => t('Connector Settings'), '$title' => t('Connector Settings'),
'$tabs' => $tabs, '$tabs' => $tabs,
'$diasp_enabled' => $diasp_enabled, '$diasp_enabled' => $diasp_enabled,
'$ostat_enabled' => $ostat_enabled, '$ostat_enabled' => $ostat_enabled,
'$h_imap' => t('Email/Mailbox Setup'),
'$imap_desc' => t("If you wish to communicate with email contacts using this service \x28optional\x29, please specify how to connect to your mailbox."),
'$imap_lastcheck' => array('imap_lastcheck', t('Last successful email check:'), $mail_chk,''),
'$mail_disabled' => (($mail_disabled) ? t('Email access is disabled on this site.') : ''),
'$mail_server' => array('mail_server', t('IMAP server name:'), $mail_server, ''),
'$mail_port' => array('mail_port', t('IMAP port:'), $mail_port, ''),
'$mail_ssl' => array('mail_ssl', t('Security:'), strtoupper($mail_ssl), '', array( ''=>t('None'), 'TLS'=>'TLS', 'SSL'=>'SSL')),
'$mail_user' => array('mail_user', t('Email login name:'), $mail_user, ''),
'$mail_pass' => array('mail_pass', t('Email password:'), '', ''),
'$mail_replyto' => array('mail_replyto', t('Reply-to address:'), '', 'Optional'),
'$mail_pubmail' => array('mail_pubmail', t('Send public posts to all email contacts:'), $mail_pubmail, ''),
'$mail_action' => array('mail_action', t('Action after import:'), $mail_action, '', array(0=>t('None'), 1=>t('Delete'), 2=>t('Mark as seen'), 3=>t('Move to folder'))),
'$mail_movetofolder' => array('mail_movetofolder', t('Move to folder:'), $mail_movetofolder, ''),
'$submit' => t('Submit'),
'$h_imap' => t('Email/Mailbox Setup'),
'$imap_desc' => t("If you wish to communicate with email contacts using this service \x28optional\x29, please specify how to connect to your mailbox."),
'$imap_lastcheck' => array('imap_lastcheck', t('Last successful email check:'), $mail_chk,''),
'$mail_disabled' => (($mail_disabled) ? t('Email access is disabled on this site.') : ''),
'$mail_server' => array('mail_server', t('IMAP server name:'), $mail_server, ''),
'$mail_port' => array('mail_port', t('IMAP port:'), $mail_port, ''),
'$mail_ssl' => array('mail_ssl', t('Security:'), strtoupper($mail_ssl), '', array( ''=>t('None'), 'TLS'=>'TLS', 'SSL'=>'SSL')),
'$mail_user' => array('mail_user', t('Email login name:'), $mail_user, ''),
'$mail_pass' => array('mail_pass', t('Email password:'), '', ''),
'$mail_replyto' => array('mail_replyto', t('Reply-to address:'), '', 'Optional'),
'$mail_pubmail' => array('mail_pubmail', t('Send public posts to all email contacts:'), $mail_pubmail, ''),
'$mail_action' => array('mail_action', t('Action after import:'), $mail_action, '', array(0=>t('None'), 1=>t('Delete'), 2=>t('Mark as seen'), 3=>t('Move to folder'))),
'$mail_movetofolder' => array('mail_movetofolder', t('Move to folder:'), $mail_movetofolder, ''),
'$submit' => t('Submit'),
'$settings_connectors' => $settings_connectors '$settings_connectors' => $settings_connectors
)); ));
@ -805,6 +821,7 @@ function settings_content(&$a) {
'$submit' => t('Submit'), '$submit' => t('Submit'),
'$baseurl' => $a->get_baseurl(), '$baseurl' => $a->get_baseurl(),
'$uid' => local_user(), '$uid' => local_user(),
'$form_security_token' => get_form_security_token("settings"),
'$nickname_block' => $prof_addr, '$nickname_block' => $prof_addr,

View file

@ -40,6 +40,7 @@ $desc
</script> </script>
<form action="profile_photo/$resource" id="crop-image-form" method="post" /> <form action="profile_photo/$resource" id="crop-image-form" method="post" />
<input type='hidden' name='form_security_token' value='$form_security_token'>
<input type="hidden" name="cropfinal" value="1" /> <input type="hidden" name="cropfinal" value="1" />
<input type="hidden" name="xstart" id="x1" /> <input type="hidden" name="xstart" id="x1" />

View file

@ -5,9 +5,9 @@ $default
<div id="profile-edit-links"> <div id="profile-edit-links">
<ul> <ul>
<li><a href="profile/$profile_id/view?tab=profile" id="profile-edit-view-link" title="$viewprof">$viewprof</a></li> <li><a href="profile/$profile_id/view?tab=profile" id="profile-edit-view-link" title="$viewprof">$viewprof</a></li>
<li><a href="profiles/clone/$profile_id" id="profile-edit-clone-link" title="$cr_prof">$cl_prof</a></li> <li><a href="$profile_clone_link" id="profile-edit-clone-link" title="$cr_prof">$cl_prof</a></li>
<li></li> <li></li>
<li><a href="profiles/drop/$profile_id" id="profile-edit-drop-link" title="$del_prof" $disabled >$del_prof</a></li> <li><a href="$profile_drop_link" id="profile-edit-drop-link" title="$del_prof" $disabled >$del_prof</a></li>
</ul> </ul>
</div> </div>
@ -17,6 +17,7 @@ $default
<div id="profile-edit-wrapper" > <div id="profile-edit-wrapper" >
<form id="profile-edit-form" name="form1" action="profiles/$profile_id" method="post" > <form id="profile-edit-form" name="form1" action="profiles/$profile_id" method="post" >
<input type='hidden' name='form_security_token' value='$form_security_token'>
<div id="profile-edit-profile-name-wrapper" > <div id="profile-edit-profile-name-wrapper" >
<label id="profile-edit-profile-name-label" for="profile-edit-profile-name" >$lbl_profname </label> <label id="profile-edit-profile-name-label" for="profile-edit-profile-name" >$lbl_profname </label>

View file

@ -3,6 +3,6 @@
<a href="profile_photo" >$chg_photo</a> <a href="profile_photo" >$chg_photo</a>
</p> </p>
<div id="profile-listing-new-link-wrapper" class="button" > <div id="profile-listing-new-link-wrapper" class="button" >
<a href="profiles/new" id="profile-listing-new-link" title="$cr_new" >$cr_new</a> <a href="$cr_new_link" id="profile-listing-new-link" title="$cr_new" >$cr_new</a>
</div> </div>

View file

@ -1,6 +1,7 @@
<h1>$title</h1> <h1>$title</h1>
<form enctype="multipart/form-data" action="profile_photo" method="post"> <form enctype="multipart/form-data" action="profile_photo" method="post">
<input type='hidden' name='form_security_token' value='$form_security_token'>
<div id="profile-photo-upload-wrapper"> <div id="profile-photo-upload-wrapper">
<label id="profile-photo-upload-label" for="profile-photo-upload">$lbl_upfile </label> <label id="profile-photo-upload-label" for="profile-photo-upload">$lbl_upfile </label>

View file

@ -5,7 +5,7 @@ $tabs
$nickname_block $nickname_block
<form action="settings" id="settings-form" method="post" autocomplete="off" > <form action="settings" id="settings-form" method="post" autocomplete="off" >
<input type='hidden' name='form_security_token' value='$form_security_token'>
<h3 class="settings-heading">$h_pass</h3> <h3 class="settings-heading">$h_pass</h3>

View file

@ -4,6 +4,7 @@ $tabs
<form action="settings/addon" method="post" autocomplete="off"> <form action="settings/addon" method="post" autocomplete="off">
<input type='hidden' name='form_security_token' value='$form_security_token'>
$settings_addons $settings_addons

View file

@ -6,6 +6,7 @@ $tabs
<div class="connector_statusmsg">$ostat_enabled</div> <div class="connector_statusmsg">$ostat_enabled</div>
<form action="settings/connectors" method="post" autocomplete="off"> <form action="settings/connectors" method="post" autocomplete="off">
<input type='hidden' name='form_security_token' value='$form_security_token'>
$settings_connectors $settings_connectors

View file

@ -4,6 +4,7 @@ $tabs
<form action="settings/oauth" method="post" autocomplete="off"> <form action="settings/oauth" method="post" autocomplete="off">
<input type='hidden' name='form_security_token' value='$form_security_token'>
<div id="profile-edit-links"> <div id="profile-edit-links">
<ul> <ul>
@ -24,7 +25,7 @@ $tabs
{{ endif }} {{ endif }}
{{ if $app.my }} {{ if $app.my }}
<a href="$baseurl/settings/oauth/edit/$app.client_id" class="icon s22 edit" title="$edit">&nbsp;</a> <a href="$baseurl/settings/oauth/edit/$app.client_id" class="icon s22 edit" title="$edit">&nbsp;</a>
<a href="$baseurl/settings/oauth/delete/$app.client_id" class="icon s22 delete" title="$delete">&nbsp;</a> <a href="$baseurl/settings/oauth/delete/$app.client_id?t=$form_security_token" class="icon s22 delete" title="$delete">&nbsp;</a>
{{ endif }} {{ endif }}
</div> </div>
{{ endfor }} {{ endfor }}

View file

@ -3,6 +3,8 @@ $tabs
<h1>$title</h1> <h1>$title</h1>
<form method="POST"> <form method="POST">
<input type='hidden' name='form_security_token' value='$form_security_token'>
{{ inc field_input.tpl with $field=$name }}{{ endinc }} {{ inc field_input.tpl with $field=$name }}{{ endinc }}
{{ inc field_input.tpl with $field=$key }}{{ endinc }} {{ inc field_input.tpl with $field=$key }}{{ endinc }}
{{ inc field_input.tpl with $field=$secret }}{{ endinc }} {{ inc field_input.tpl with $field=$secret }}{{ endinc }}