Merge branch 'dev' into oauth2

This commit is contained in:
Andrew Manning 2018-02-22 15:10:20 -05:00
commit 70719c67d3
19 changed files with 162 additions and 159 deletions

View File

@ -1,5 +1,5 @@
<?php <?php
namespace Zotlabs\Zot; namespace Zotlabs\Lib;
class DReport { class DReport {

View File

@ -1,19 +0,0 @@
<?php /** @file */
namespace Zotlabs\Lib;
/*
* Abstraction class for dealing with alternate networks (which of course do not exist, hence the abstraction)
*/
abstract class ProtoDriver {
abstract protected function discover($channel,$location);
abstract protected function deliver($item,$channel,$recipients);
abstract protected function collect($channel,$connection);
abstract protected function change_permissions($permissions,$channel,$recipient);
abstract protected function acknowledge_permissions($permissions,$channel,$recipient);
abstract protected function deliver_private($item,$channel,$recipients);
abstract protected function collect_private($channel,$connection);
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Zotlabs\Zot; namespace Zotlabs\Lib;
class Verify { class Verify {

View File

@ -1,30 +0,0 @@
<?php /** @file */
namespace Zotlabs\Lib;
class ZotDriver extends ProtoDriver {
protected function discover($channel,$location) {
}
protected function deliver($item,$channel,$recipients) {
}
protected function collect($channel,$connection) {
}
protected function change_permissions($permissions,$channel,$recipient) {
}
protected function acknowledge_permissions($permissions,$channel,$recipient) {
}
protected function deliver_private($item,$channel,$recipients) {
}
protected function collect_private($channel,$connection) {
}
}

View File

@ -255,7 +255,7 @@ class Channel extends \Zotlabs\Web\Controller {
AND (abook.abook_blocked = 0 or abook.abook_flags is null) AND (abook.abook_blocked = 0 or abook.abook_flags is null)
AND item.item_wall = 1 AND item.item_wall = 1
$sql_extra $sql_extra2 $sql_extra $sql_extra2
ORDER BY created DESC $pager_sql ", ORDER BY created DESC, id $pager_sql ",
intval(\App::$profile['profile_uid']) intval(\App::$profile['profile_uid'])
); );
} }

View File

@ -166,7 +166,7 @@ class Magic extends \Zotlabs\Web\Controller {
$token = random_string(); $token = random_string();
\Zotlabs\Zot\Verify::create('auth',$channel['channel_id'],$token,$x[0]['hubloc_url']); \Zotlabs\Lib\Verify::create('auth',$channel['channel_id'],$token,$x[0]['hubloc_url']);
$target_url = $x[0]['hubloc_callback'] . '/?f=&auth=' . urlencode(channel_reddress($channel)) $target_url = $x[0]['hubloc_callback'] . '/?f=&auth=' . urlencode(channel_reddress($channel))
. '&sec=' . $token . '&dest=' . urlencode($dest) . '&version=' . ZOT_REVISION; . '&sec=' . $token . '&dest=' . urlencode($dest) . '&version=' . ZOT_REVISION;

View File

@ -142,6 +142,7 @@ class New_channel extends \Zotlabs\Web\Controller {
'$role' => $role, '$role' => $role,
'$default_role' => $default_role, '$default_role' => $default_role,
'$nickname' => $nickname, '$nickname' => $nickname,
'$validate' => t('Validate'),
'$submit' => t('Create'), '$submit' => t('Create'),
'$channel_usage_message' => $channel_usage_message '$channel_usage_message' => $channel_usage_message
)); ));

View File

@ -42,7 +42,7 @@ class Owa extends \Zotlabs\Web\Controller {
logger('OWA success: ' . $hubloc['hubloc_addr'],LOGGER_DATA); logger('OWA success: ' . $hubloc['hubloc_addr'],LOGGER_DATA);
$ret['success'] = true; $ret['success'] = true;
$token = random_string(32); $token = random_string(32);
\Zotlabs\Zot\Verify::create('owt',0,$token,$hubloc['hubloc_addr']); \Zotlabs\Lib\Verify::create('owt',0,$token,$hubloc['hubloc_addr']);
$result = ''; $result = '';
openssl_public_encrypt($token,$result,$hubloc['xchan_pubkey']); openssl_public_encrypt($token,$result,$hubloc['xchan_pubkey']);
$ret['encrypted_token'] = base64url_encode($result); $ret['encrypted_token'] = base64url_encode($result);

View File

@ -50,7 +50,7 @@ require_once('include/attach.php');
require_once('include/bbcode.php'); require_once('include/bbcode.php');
define ( 'PLATFORM_NAME', 'hubzilla' ); define ( 'PLATFORM_NAME', 'hubzilla' );
define ( 'STD_VERSION', '3.1.11' ); define ( 'STD_VERSION', '3.1.12' );
define ( 'ZOT_REVISION', '1.3' ); define ( 'ZOT_REVISION', '1.3' );
define ( 'DB_UPDATE_VERSION', 1203 ); define ( 'DB_UPDATE_VERSION', 1203 );

View File

@ -2553,10 +2553,10 @@ function channel_remove($channel_id, $local = true, $unset_session = false) {
q("DELETE FROM profile WHERE uid = %d", intval($channel_id)); q("DELETE FROM profile WHERE uid = %d", intval($channel_id));
q("DELETE FROM src WHERE src_channel_id = %d", intval($channel_id)); q("DELETE FROM src WHERE src_channel_id = %d", intval($channel_id));
$r = q("select resource_id FROM attach WHERE uid = %d", intval($channel_id)); $r = q("select hash FROM attach WHERE uid = %d", intval($channel_id));
if($r) { if($r) {
foreach($r as $rv) { foreach($r as $rv) {
attach_delete($channel_id,$rv['resource_id']); attach_delete($channel_id,$rv['hash']);
} }
} }

View File

@ -119,21 +119,27 @@ function crypto_encapsulate($data,$pubkey,$alg='aes256cbc') {
} }
function other_encapsulate($data,$pubkey,$alg) { function other_encapsulate($data,$pubkey,$alg) {
if(! $pubkey) if(! $pubkey)
logger('no key. data: ' . $data); logger('no key. data: ' . $data);
$oaep = false; // This default will change in the future. For now make it backward compatible.
if(strpos($alg,'.oaep')) { $padding = OPENSSL_PKCS1_PADDING;
$oaep = true; $base = $alg;
$subalg = substr($alg,0,-5);
$exts = explode('.',$alg);
if(count($exts) > 1) {
switch($exts[1]) {
case 'oaep':
$padding = OPENSSL_PKCS1_OAEP_PADDING;
break;
} }
else { $base = $exts[0];
$subalg = $alg;
} }
$fn = strtoupper($subalg) . '_encrypt'; $fn = strtoupper($base) . '_encrypt';
if(function_exists($fn)) { if(function_exists($fn)) {
// A bit hesitant to use openssl_random_pseudo_bytes() as we know // A bit hesitant to use openssl_random_pseudo_bytes() as we know
@ -151,14 +157,14 @@ function other_encapsulate($data,$pubkey,$alg) {
$iv = openssl_random_pseudo_bytes(256); $iv = openssl_random_pseudo_bytes(256);
$result['data'] = base64url_encode($fn($data,$key,$iv),true); $result['data'] = base64url_encode($fn($data,$key,$iv),true);
// log the offending call so we can track it down // log the offending call so we can track it down
if(! openssl_public_encrypt($key,$k,$pubkey,(($oaep) ? OPENSSL_PKCS1_OAEP_PADDING : OPENSSL_PKCS1_PADDING))) { if(! openssl_public_encrypt($key,$k,$pubkey,$padding)) {
$x = debug_backtrace(); $x = debug_backtrace();
logger('RSA failed. ' . print_r($x[0],true)); logger('RSA failed. ' . print_r($x[0],true));
} }
$result['alg'] = $alg; $result['alg'] = $alg;
$result['key'] = base64url_encode($k,true); $result['key'] = base64url_encode($k,true);
openssl_public_encrypt($iv,$i,$pubkey,(($oaep) ? OPENSSL_PKCS1_OAEP_PADDING : OPENSSL_PKCS1_PADDING)); openssl_public_encrypt($iv,$i,$pubkey,$padding);
$result['iv'] = base64url_encode($i,true); $result['iv'] = base64url_encode($i,true);
return $result; return $result;
} }
@ -229,20 +235,25 @@ function crypto_unencapsulate($data,$prvkey) {
function other_unencapsulate($data,$prvkey,$alg) { function other_unencapsulate($data,$prvkey,$alg) {
$oaep = false; // This default will change in the future. For now make it backward compatible.
if(strpos($alg,'.oaep')) { $padding = OPENSSL_PKCS1_PADDING;
$oaep = true; $base = $alg;
$subalg = substr($alg,0,-5);
$exts = explode('.',$alg);
if(count($exts) > 1) {
switch($exts[1]) {
case 'oaep':
$padding = OPENSSL_PKCS1_OAEP_PADDING;
break;
} }
else { $base = $exts[0];
$subalg = $alg;
} }
$fn = strtoupper($subalg) . '_decrypt'; $fn = strtoupper($base) . '_decrypt';
if(function_exists($fn)) { if(function_exists($fn)) {
openssl_private_decrypt(base64url_decode($data['key']),$k,$prvkey,(($oaep) ? OPENSSL_PKCS1_OAEP_PADDING : OPENSSL_PKCS1_PADDING)); openssl_private_decrypt(base64url_decode($data['key']),$k,$prvkey,$padding);
openssl_private_decrypt(base64url_decode($data['iv']),$i,$prvkey,(($oaep) ? OPENSSL_PKCS1_OAEP_PADDING : OPENSSL_PKCS1_PADDING)); openssl_private_decrypt(base64url_decode($data['iv']),$i,$prvkey,$padding);
return $fn(base64url_decode($data['data']),$k,$i); return $fn(base64url_decode($data['data']),$k,$i);
} }
else { else {

View File

@ -1200,6 +1200,13 @@ function event_store_item($arr, $event) {
)); ));
} }
// propagate the event resource_id so that posts containing it are easily searchable in downstream copies
// of the item which have not stored the actual event. Required for Diaspora event federation as Diaspora
// event_participation messages refer to the event resource_id as a parent, while out own event attendance
// activities refer to the item message_id as the parent.
set_iconfig($item_arr, 'system','event_id',$event['event_hash'],true);
$res = item_store($item_arr); $res = item_store($item_arr);
$item_id = $res['item_id']; $item_id = $res['item_id'];

View File

@ -4003,18 +4003,24 @@ function zot_feed($uid, $observer_hash, $arr) {
$item_normal = item_normal(); $item_normal = item_normal();
if(is_sys_channel($uid)) { if(is_sys_channel($uid)) {
$r = q("SELECT parent, created, postopts from item
WHERE uid != %d $nonsys_uids = q("SELECT channel_id FROM channel WHERE channel_system = 0");
$item_normal $nonsys_uids_str = ids_to_querystr($nonsys_uids,'channel_id');
$r = q("SELECT parent, postopts FROM item
WHERE uid IN ( %s )
AND item_wall = 1 AND item_wall = 1
and item_private = 0 $sql_extra ORDER BY created ASC $limit", AND item_private = 0
intval($uid) $item_normal
$sql_extra ORDER BY created ASC $limit",
intval($nonsys_uids_str)
); );
} }
else { else {
$r = q("SELECT parent, created, postopts from item $r = q("SELECT parent, postopts FROM item
WHERE uid = %d $item_normal WHERE uid = %d
AND item_wall = 1 AND item_wall = 1
$item_normal
$sql_extra ORDER BY created ASC $limit", $sql_extra ORDER BY created ASC $limit",
intval($uid) intval($uid)
); );

View File

@ -1605,10 +1605,10 @@ function get_site_info() {
'commit' => $commit, 'commit' => $commit,
'plugins' => $visible_plugins, 'plugins' => $visible_plugins,
'register_policy' => $register_policy[get_config('system','register_policy')], 'register_policy' => $register_policy[get_config('system','register_policy')],
'invitation_only' => intval(get_config('system','invitation_only')), 'invitation_only' => (bool) intval(get_config('system','invitation_only')),
'directory_mode' => $directory_mode[get_config('system','directory_mode')], 'directory_mode' => $directory_mode[get_config('system','directory_mode')],
'language' => get_config('system','language'), 'language' => get_config('system','language'),
'rss_connections' => intval(get_config('system','feed_contacts')), 'rss_connections' => (bool) intval(get_config('system','feed_contacts')),
'expiration' => $site_expire, 'expiration' => $site_expire,
'default_service_restrictions' => $service_class, 'default_service_restrictions' => $service_class,
'locked_features' => $locked_features, 'locked_features' => $locked_features,

View File

@ -245,8 +245,18 @@ function plugins_sync() {
* @return array * @return array
*/ */
function visible_plugin_list() { function visible_plugin_list() {
$r = q("select * from addon where hidden = 0 order by aname asc"); $r = q("select * from addon where hidden = 0 order by aname asc");
return(($r) ? ids_to_array($r,'aname') : array()); $x = (($r) ? ids_to_array($r,'aname') : array());
$y = [];
if($x) {
foreach($x as $xv) {
if(is_dir('addon/' . $xv)) {
$y[] = $xv;
}
}
}
return $y;
} }

View File

@ -265,9 +265,9 @@ function red_zrlify_img_callback($matches) {
*/ */
function owt_init($token) { function owt_init($token) {
\Zotlabs\Zot\Verify::purge('owt', '3 MINUTE'); \Zotlabs\Lib\Verify::purge('owt', '3 MINUTE');
$ob_hash = \Zotlabs\Zot\Verify::get_meta('owt', 0, $token); $ob_hash = \Zotlabs\Lib\Verify::get_meta('owt', 0, $token);
if($ob_hash === false) { if($ob_hash === false) {
return; return;

View File

@ -1152,7 +1152,12 @@ function zot_process_response($hub, $arr, $outq) {
* @brief * @brief
* *
* We received a notification packet (in mod_post) that a message is waiting for us, and we've verified the sender. * We received a notification packet (in mod_post) that a message is waiting for us, and we've verified the sender.
* Now send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site * Check if the site is using zot6 delivery and includes a verified HTTP Signature, signed content, and a 'msg' field,
* and also that the signer and the sender match.
* If that happens, we do not need to fetch/pickup the message - we have it already and it is verified.
* Translate it into the form we need for zot_import() and import it.
*
* Otherwise send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site
* private key. * private key.
* The entire pickup message is encrypted with the remote site's public key. * The entire pickup message is encrypted with the remote site's public key.
* If everything checks out on the remote end, we will receive back a packet containing one or more messages, * If everything checks out on the remote end, we will receive back a packet containing one or more messages,
@ -1170,18 +1175,37 @@ function zot_fetch($arr) {
$url = $arr['sender']['url'] . $arr['callback']; $url = $arr['sender']['url'] . $arr['callback'];
$import = null;
$hubs = null;
$zret = zot6_check_sig();
if($zret['success'] && $zret['hubloc'] && $zret['hubloc']['hubloc_guid'] === $data['sender']['guid'] && $data['msg']) {
logger('zot6_delivery',LOGGER_DEBUG);
logger('zot6_data: ' . print_r($data,true),LOGGER_DATA);
$ret['collected'] = true;
$import = [ 'success' => true, 'body' => json_encode( [ 'success' => true, 'pickup' => [ [ 'notify' => $data, 'message' => json_decode($data['msg'],true) ] ] ] ) ];
$hubs = [ $zret['hubloc'] ] ;
}
if(! $hubs) {
// set $multiple param on zot_gethub() to return all matching hubs // set $multiple param on zot_gethub() to return all matching hubs
// This allows us to recover from re-installs when a redundant (but invalid) hubloc for // This allows us to recover from re-installs when a redundant (but invalid) hubloc for
// this identity is widely dispersed throughout the network. // this identity is widely dispersed throughout the network.
$ret_hubs = zot_gethub($arr['sender'],true); $hubs = zot_gethub($arr['sender'],true);
if(! $ret_hubs) { }
if(! $hubs) {
logger('No hub: ' . print_r($arr['sender'],true)); logger('No hub: ' . print_r($arr['sender'],true));
return; return;
} }
foreach($ret_hubs as $ret_hub) { foreach($hubs as $hub) {
if(! $import) {
$secret = substr(preg_replace('/[^0-9a-fA-F]/','',$arr['secret']),0,64); $secret = substr(preg_replace('/[^0-9a-fA-F]/','',$arr['secret']),0,64);
$data = [ $data = [
@ -1193,15 +1217,19 @@ function zot_fetch($arr) {
'secret_sig' => base64url_encode(rsa_sign($secret, get_config('system','prvkey'))) 'secret_sig' => base64url_encode(rsa_sign($secret, get_config('system','prvkey')))
]; ];
$algorithm = zot_best_algorithm($ret_hub['site_crypto']); $algorithm = zot_best_algorithm($hub['site_crypto']);
$datatosend = json_encode(crypto_encapsulate(json_encode($data),$ret_hub['hubloc_sitekey'], $algorithm)); $datatosend = json_encode(crypto_encapsulate(json_encode($data),$hub['hubloc_sitekey'], $algorithm));
$fetch = zot_zot($url,$datatosend); $import = zot_zot($url,$datatosend);
}
else {
$algorithm = zot_best_algorithm($hub['site_crypto']);
}
$result = zot_import($fetch, $arr['sender']['url']); $result = zot_import($import, $arr['sender']['url']);
if($result) { if($result) {
$result = crypto_encapsulate(json_encode($result),$ret_hub['hubloc_sitekey'], $algorithm); $result = crypto_encapsulate(json_encode($result),$hub['hubloc_sitekey'], $algorithm);
return $result; return $result;
} }
@ -1700,7 +1728,7 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $
foreach($deliveries as $d) { foreach($deliveries as $d) {
$local_public = $public; $local_public = $public;
$DR = new Zotlabs\Zot\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']); $DR = new Zotlabs\Lib\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']);
$r = q("select * from channel where channel_hash = '%s' limit 1", $r = q("select * from channel where channel_hash = '%s' limit 1",
dbesc($d['hash']) dbesc($d['hash'])
@ -2229,7 +2257,7 @@ function process_mail_delivery($sender, $arr, $deliveries) {
foreach($deliveries as $d) { foreach($deliveries as $d) {
$DR = new Zotlabs\Zot\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']); $DR = new Zotlabs\Lib\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']);
$r = q("select * from channel where channel_hash = '%s' limit 1", $r = q("select * from channel where channel_hash = '%s' limit 1",
dbesc($d['hash']) dbesc($d['hash'])
@ -3870,11 +3898,11 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) {
// we should probably do this for all items, but usually we only send one. // we should probably do this for all items, but usually we only send one.
if(array_key_exists('item',$arr) && is_array($arr['item'][0])) { if(array_key_exists('item',$arr) && is_array($arr['item'][0])) {
$DR = new Zotlabs\Zot\DReport(z_root(),$d['hash'],$d['hash'],$arr['item'][0]['message_id'],'channel sync processed'); $DR = new Zotlabs\Lib\DReport(z_root(),$d['hash'],$d['hash'],$arr['item'][0]['message_id'],'channel sync processed');
$DR->addto_recipient($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); $DR->addto_recipient($channel['channel_name'] . ' <' . channel_reddress($channel) . '>');
} }
else else
$DR = new Zotlabs\Zot\DReport(z_root(),$d['hash'],$d['hash'],'sync packet','channel sync delivered'); $DR = new Zotlabs\Lib\DReport(z_root(),$d['hash'],$d['hash'],'sync packet','channel sync delivered');
$result[] = $DR->get(); $result[] = $DR->get();
} }
@ -4885,7 +4913,7 @@ function zot_reply_auth_check($data,$encrypted_packet) {
* the web server. We should probably convert this to webserver time rather than DB time so * the web server. We should probably convert this to webserver time rather than DB time so
* that the different clocks won't affect it and allow us to keep the time short. * that the different clocks won't affect it and allow us to keep the time short.
*/ */
Zotlabs\Zot\Verify::purge('auth', '30 MINUTE'); Zotlabs\Lib\Verify::purge('auth', '30 MINUTE');
$y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1",
dbesc($sender_hash) dbesc($sender_hash)
@ -4926,7 +4954,7 @@ function zot_reply_auth_check($data,$encrypted_packet) {
// This additionally checks for forged sites since we already stored the expected result in meta // This additionally checks for forged sites since we already stored the expected result in meta
// and we've already verified that this is them via zot_gethub() and that their key signed our token // and we've already verified that this is them via zot_gethub() and that their key signed our token
$z = Zotlabs\Zot\Verify::match('auth',$c[0]['channel_id'],$data['secret'],$data['sender']['url']); $z = Zotlabs\Lib\Verify::match('auth',$c[0]['channel_id'],$data['secret'],$data['sender']['url']);
if (! $z) { if (! $z) {
logger('mod_zot: auth_check: verification key not found.'); logger('mod_zot: auth_check: verification key not found.');
$ret['message'] .= 'verification key not found' . EOL; $ret['message'] .= 'verification key not found' . EOL;
@ -5098,29 +5126,6 @@ function zot_reply_notify($data) {
logger('notify received from ' . $data['sender']['url']); logger('notify received from ' . $data['sender']['url']);
// handle zot6 delivery
$zret = zot6_check_sig();
if($zret['success'] && $zret['hubloc'] && $zret['hubloc']['hubloc_guid'] === $data['sender']['guid'] && $data['msg']) {
logger('zot6_delivery',LOGGER_DEBUG);
logger('zot6_data: ' . print_r($data,true),LOGGER_DATA);
$ret['collected'] = true;
$import = [ 'success' => true, 'pickup' => [ [ 'notify' => $data, 'message' => json_decode($data['msg'],true) ] ] ];
logger('zot6_import: ' . print_r($import,true), LOGGER_DATA);
$x = zot_import([ 'success' => true, 'body' => json_encode($import) ], $data['sender']['url']);
if($x) {
$x = crypto_encapsulate(json_encode($x),$zret['hubloc']['hubloc_sitekey'],zot_best_algorithm($zret['hubloc']['site_crypto']));
$ret['delivery_report'] = $x;
}
}
else {
// handle traditional zot delivery
$async = get_config('system','queued_fetch'); $async = get_config('system','queued_fetch');
if($async) { if($async) {
@ -5131,7 +5136,6 @@ function zot_reply_notify($data) {
$x = zot_fetch($data); $x = zot_fetch($data);
$ret['delivery_report'] = $x; $ret['delivery_report'] = $x;
} }
}
$ret['success'] = true; $ret['success'] = true;
json_return_and_die($ret); json_return_and_die($ret);

View File

@ -1,5 +1,7 @@
$(document).ready(function() { $(document).ready(function() {
// $("#id_permissions_role").sSelect();
$("#newchannel-submit-button").attr('disabled','disabled');
$("#id_name").blur(function() { $("#id_name").blur(function() {
$("#name-spinner").show(); $("#name-spinner").show();
var zreg_name = $("#id_name").val(); var zreg_name = $("#id_name").val();
@ -13,7 +15,14 @@
}); });
}); });
$("#id_nickname").blur(function() { $("#id_nickname").click(function() {
$("#newchannel-submit-button").attr('disabled','disabled');
});
});
function validate_channel() {
$("#nick-spinner").show(); $("#nick-spinner").show();
var zreg_nick = $("#id_nickname").val(); var zreg_nick = $("#id_nickname").val();
$.get("new_channel/checkaddr.json?f=&nick=" + encodeURIComponent(zreg_nick),function(data) { $.get("new_channel/checkaddr.json?f=&nick=" + encodeURIComponent(zreg_nick),function(data) {
@ -22,8 +31,10 @@
$("#help_nickname").html(""); $("#help_nickname").html("");
zFormError("#help_nickname",data.error); zFormError("#help_nickname",data.error);
} }
else {
$("#newchannel-submit-button").removeAttr('disabled');
}
$("#nick-spinner").hide(); $("#nick-spinner").hide();
}); });
});
}); }

View File

@ -24,7 +24,9 @@
{{include file="field_input.tpl" field=$nickname}} {{include file="field_input.tpl" field=$nickname}}
<div id="nick-spinner" class="spinner-wrapper"><div class="spinner m"></div></div> <div id="nick-spinner" class="spinner-wrapper"><div class="spinner m"></div></div>
<button class="btn btn-primary" type="submit" name="submit" id="newchannel-submit-button" value="{{$submit}}">{{$submit}}</button> <button class="btn btn-secondary" name="validate" id="newchannel-validate-button" value="{{$validate}}" onclick="validate_channel(); return false;">{{$validate}}</button>
<button class="btn btn-primary" type="submit" name="submit" id="newchannel-submit-button" value="{{$submit}}" >{{$submit}}</button>
<div id="newchannel-submit-end" class="clear"></div> <div id="newchannel-submit-end" class="clear"></div>
<div id="newchannel-import-link" class="descriptive-paragraph" >{{$label_import}}</div> <div id="newchannel-import-link" class="descriptive-paragraph" >{{$label_import}}</div>