mirror of https://github.com/pixelfed/pixelfed
commit
f67bb5028e
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Util\Localization;
|
||||||
|
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class Localization {
|
||||||
|
|
||||||
|
public static function languages()
|
||||||
|
{
|
||||||
|
return Cache::remember('core:localization:languages', now()->addDays(14), function() {
|
||||||
|
$dir = resource_path('lang');
|
||||||
|
return Arr::flatten(array_diff(scandir($dir), array('..', '.', 'vendor')));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,437 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<input type="file" name="media" class="d-none file-input" multiple="">
|
||||||
|
<div class="timeline">
|
||||||
|
<div class="card status-card card-md-rounded-0">
|
||||||
|
|
||||||
|
<div class="card-header d-inline-flex align-items-center bg-white">
|
||||||
|
<img v-bind:src="profile.avatar" width="32px" height="32px" style="border-radius: 32px;" class="box-shadow">
|
||||||
|
<a class="username font-weight-bold pl-2 text-dark" v-bind:href="profile.url">
|
||||||
|
{{profile.username}}
|
||||||
|
</a>
|
||||||
|
<div class="text-right" style="flex-grow:1;">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||||
|
<span class="fas fa-ellipsis-v fa-lg text-muted"></span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||||
|
<div class="dropdown-item small font-weight-bold" v-on:click="mediaDrawer = !mediaDrawer">Show Media Toolbar</div>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="postPresenterContainer">
|
||||||
|
|
||||||
|
|
||||||
|
<div v-if="ids.length == 0" class="w-100 h-100 bg-light py-5 cursor-pointer" style="border-bottom: 1px solid #f1f1f1" v-on:click="addMedia()">
|
||||||
|
<p class="text-center mb-0 font-weight-bold p-5">Click here to add photos.</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="ids.length > 0">
|
||||||
|
|
||||||
|
<b-carousel id="p-carousel"
|
||||||
|
style="text-shadow: 1px 1px 2px #333;"
|
||||||
|
controls
|
||||||
|
indicators
|
||||||
|
background="#ffffff"
|
||||||
|
:interval="0"
|
||||||
|
v-model="carouselCursor"
|
||||||
|
>
|
||||||
|
<b-carousel-slide v-if="ids.length > 0" v-for="(preview, index) in media" :key="'preview_media_'+index">
|
||||||
|
<div slot="img" :class="[media[index].filter_class?media[index].filter_class + ' cursor-pointer':' cursor-pointer']" v-on:click="addMedia()">
|
||||||
|
<img class="d-block img-fluid w-100" :src="preview.url" :alt="preview.description" :title="preview.description">
|
||||||
|
</div>
|
||||||
|
</b-carousel-slide>
|
||||||
|
</b-carousel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="mediaDrawer" class="bg-lighter p-2 row">
|
||||||
|
<div class="col-4">
|
||||||
|
<select class="form-control form-control-sm" id="filterSelectDropdown" v-on:change="toggleFilter($event)">
|
||||||
|
<option value="none">No filter</option>
|
||||||
|
<option v-for="(filter, index) in filters" :value="filter[1]" :selected="filter[1]==media[carouselCursor].filter_class?'selected':''">{{filter[0]}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<button class="btn btn-outline-primary btn-sm mr-1" v-on:click="mediaAltText()">Alt Text</button>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mr-1" v-on:click="mediaLicense()">License</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-right">
|
||||||
|
<button class="btn btn-outline-danger btn-sm font-weight-bold mr-1" v-on:click="deleteMedia()"><i class="fas fa-trash"></i></button>
|
||||||
|
<button class="btn btn-outline-secondary btn-sm font-weight-bold" v-on:click="updateMedia()"><i class="fas fa-times"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="[mediaDrawer?'glass card-body disabled':'card-body']">
|
||||||
|
<div class="reactions my-1">
|
||||||
|
<h3 class="far fa-heart pr-3 m-0 text-lighter" title="Like"></h3>
|
||||||
|
<h3 class="far fa-comment pr-3 m-0 text-lighter" title="Comment"></h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="likes font-weight-bold">
|
||||||
|
<span class="like-count">0</span> likes
|
||||||
|
</div>
|
||||||
|
<div class="caption">
|
||||||
|
<p class="mb-2 read-more" style="overflow: hidden;">
|
||||||
|
<span class="username font-weight-bold d-inline-block clearfix">
|
||||||
|
<bdi><a class="text-dark" :href="profile.url">{{profile.username}}</a></bdi>
|
||||||
|
</span>
|
||||||
|
<span contenteditable="" style="outline:none;" v-on:keyup="textWatcher"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="comments">
|
||||||
|
</div>
|
||||||
|
<div class="timestamp pt-1">
|
||||||
|
<p class="small text-uppercase mb-0">
|
||||||
|
<span class="text-muted">
|
||||||
|
Draft
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="[mediaDrawer?'glass card-footer':'card-footer']">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<div class="custom-control custom-switch d-inline mr-3">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="nsfwToggle" v-model="nsfw">
|
||||||
|
<label class="custom-control-label small font-weight-bold text-muted pt-1" for="nsfwToggle">NSFW</label>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown d-inline">
|
||||||
|
<button class="btn btn-outline-secondary btn-sm py-0 dropdown-toggle" type="button" id="visibility" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
Public
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu" aria-labelledby="visibility" style="width: 200px;">
|
||||||
|
<a class="dropdown-item active" href="#" data-id="public" data-title="Public">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-2 px-0 text-center">
|
||||||
|
<i class="fas fa-globe"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-10 pl-2">
|
||||||
|
<p class="font-weight-bold mb-0">Public</p>
|
||||||
|
<p class="small mb-0">Anyone can see</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" data-id="private" data-title="Followers Only">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-2 px-0 text-center">
|
||||||
|
<i class="fas fa-lock"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-10 pl-2">
|
||||||
|
<p class="font-weight-bold mb-0">Followers Only</p>
|
||||||
|
<p class="small mb-0">Only followers can see</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="dropdown-item" href="#" data-id="circle" data-title="Circle">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-2 px-0 text-center">
|
||||||
|
<i class="far fa-circle"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-10 pl-2">
|
||||||
|
<p class="font-weight-bold mb-0">Circle</p>
|
||||||
|
<p class="small mb-0">Select a circle</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" data-id="direct" data-title="Direct Message">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-2 px-0 text-center">
|
||||||
|
<i class="fas fa-envelope"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-10 pl-2">
|
||||||
|
<p class="font-weight-bold mb-0">Direct Message</p>
|
||||||
|
<p class="small mb-0">Recipients only</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted font-weight-bold">
|
||||||
|
{{composeTextLength}} / 500
|
||||||
|
</div>
|
||||||
|
<div class="pl-md-5">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm font-weight-bold" v-on:click="compose()">{{composeState[0].toUpperCase() + composeState.slice(1)}}</button>
|
||||||
|
<button type="button" class="btn btn-primary btn-sm dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="sr-only">Toggle Dropdown</span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a :class="[composeState == 'publish' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href="#" v-on:click.prevent="composeState = 'publish'">Publish now</a>
|
||||||
|
<a :class="[composeState == 'draft' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href="#" v-on:click.prevent="composeState = 'draft'">Save as draft</a>
|
||||||
|
<a :class="[composeState == 'schedule' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href="#" v-on:click.prevent="composeState = 'schedule'">Schedule for later</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a :class="[composeState == 'delete' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href="#" v-on:click.prevent="composeState = 'delete'">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style type="text/css" scoped>
|
||||||
|
.glass {
|
||||||
|
-webkit-filter: blur(2px);
|
||||||
|
-moz-filter: blur(2px);
|
||||||
|
-o-filter: blur(2px);
|
||||||
|
-ms-filter: blur(2px);
|
||||||
|
filter: blur(2px);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
profile: {},
|
||||||
|
create: {
|
||||||
|
hasGeneratedSelect: false,
|
||||||
|
selectedFilter: false,
|
||||||
|
currentFilterName: false,
|
||||||
|
currentFilterClass: false
|
||||||
|
},
|
||||||
|
composeText: '',
|
||||||
|
composeTextLength: 27,
|
||||||
|
nsfw: false,
|
||||||
|
filters: [],
|
||||||
|
ids: [],
|
||||||
|
media: [],
|
||||||
|
meta: {
|
||||||
|
'id': false,
|
||||||
|
'cursor': false,
|
||||||
|
'cw': false,
|
||||||
|
'alt': null,
|
||||||
|
'filter': null,
|
||||||
|
'license': null,
|
||||||
|
'preserve_exif': false,
|
||||||
|
},
|
||||||
|
cursor: 1,
|
||||||
|
carouselCursor: 0,
|
||||||
|
visibility: 'public',
|
||||||
|
cropmode: false,
|
||||||
|
croppie: false,
|
||||||
|
limit: pixelfed.settings.maxAlbumLength,
|
||||||
|
acceptedMimes: pixelfed.settings.acceptedMimes,
|
||||||
|
mediaDrawer: false,
|
||||||
|
composeState: 'publish',
|
||||||
|
filter: {
|
||||||
|
name: null,
|
||||||
|
class: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeMount() {
|
||||||
|
this.fetchProfile();
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.mediaWatcher();
|
||||||
|
this.filters = [
|
||||||
|
['1977','filter-1977'],
|
||||||
|
['Aden','filter-aden'],
|
||||||
|
['Amaro','filter-amaro'],
|
||||||
|
['Ashby','filter-ashby'],
|
||||||
|
['Brannan','filter-brannan'],
|
||||||
|
['Brooklyn','filter-brooklyn'],
|
||||||
|
['Charmes','filter-charmes'],
|
||||||
|
['Clarendon','filter-clarendon'],
|
||||||
|
['Crema','filter-crema'],
|
||||||
|
['Dogpatch','filter-dogpatch'],
|
||||||
|
['Earlybird','filter-earlybird'],
|
||||||
|
['Gingham','filter-gingham'],
|
||||||
|
['Ginza','filter-ginza'],
|
||||||
|
['Hefe','filter-hefe'],
|
||||||
|
['Helena','filter-helena'],
|
||||||
|
['Hudson','filter-hudson'],
|
||||||
|
['Inkwell','filter-inkwell'],
|
||||||
|
['Kelvin','filter-kelvin'],
|
||||||
|
['Kuno','filter-juno'],
|
||||||
|
['Lark','filter-lark'],
|
||||||
|
['Lo-Fi','filter-lofi'],
|
||||||
|
['Ludwig','filter-ludwig'],
|
||||||
|
['Maven','filter-maven'],
|
||||||
|
['Mayfair','filter-mayfair'],
|
||||||
|
['Moon','filter-moon'],
|
||||||
|
['Nashville','filter-nashville'],
|
||||||
|
['Perpetua','filter-perpetua'],
|
||||||
|
['Poprocket','filter-poprocket'],
|
||||||
|
['Reyes','filter-reyes'],
|
||||||
|
['Rise','filter-rise'],
|
||||||
|
['Sierra','filter-sierra'],
|
||||||
|
['Skyline','filter-skyline'],
|
||||||
|
['Slumber','filter-slumber'],
|
||||||
|
['Stinson','filter-stinson'],
|
||||||
|
['Sutro','filter-sutro'],
|
||||||
|
['Toaster','filter-toaster'],
|
||||||
|
['Valencia','filter-valencia'],
|
||||||
|
['Vesper','filter-vesper'],
|
||||||
|
['Walden','filter-walden'],
|
||||||
|
['Willow','filter-willow'],
|
||||||
|
['X-Pro II','filter-xpro-ii']
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
composeText: function (newComposeText, oldComposeText) {
|
||||||
|
this.debouncedTextWatcher();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function() {
|
||||||
|
this.debouncedTextWatcher = _.debounce(this.textWatcher, 300)
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
fetchProfile() {
|
||||||
|
axios.get('/api/v1/accounts/verify_credentials').then(res => {
|
||||||
|
this.profile = res.data;
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addMedia() {
|
||||||
|
let el = $(event.target);
|
||||||
|
el.attr('disabled', '');
|
||||||
|
let fi = $('.file-input[name="media"]');
|
||||||
|
fi.trigger('click');
|
||||||
|
el.blur();
|
||||||
|
el.removeAttr('disabled');
|
||||||
|
},
|
||||||
|
|
||||||
|
textWatcher() {
|
||||||
|
this.composeText = event.target.innerText;
|
||||||
|
this.composeTextLength = event.target.innerText.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
mediaWatcher() {
|
||||||
|
let self = this;
|
||||||
|
$(document).on('change', '.file-input', function(e) {
|
||||||
|
let io = document.querySelector('.file-input');
|
||||||
|
Array.prototype.forEach.call(io.files, function(io, i) {
|
||||||
|
if(self.media && self.media.length + i >= self.limit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let type = io.type;
|
||||||
|
let acceptedMimes = pixelfed.settings.acceptedMimes.split(',');
|
||||||
|
let validated = $.inArray(type, acceptedMimes);
|
||||||
|
if(validated == -1) {
|
||||||
|
swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+pixelfed.uploader.acceptedMimes+' only.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let form = new FormData();
|
||||||
|
form.append('file', io);
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
onUploadProgress: function(e) {
|
||||||
|
let progress = Math.round( (e.loaded * 100) / e.total );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
axios.post('/api/v1/media', form, config)
|
||||||
|
.then(function(e) {
|
||||||
|
self.ids.push(e.data.id);
|
||||||
|
self.media.push(e.data);
|
||||||
|
self.mediaDrawer = true;
|
||||||
|
}).catch(function(e) {
|
||||||
|
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
||||||
|
});
|
||||||
|
io.value = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleFilter(e) {
|
||||||
|
this.media[this.carouselCursor].filter_class = e.target.value;
|
||||||
|
|
||||||
|
// todo: deprecate
|
||||||
|
this.create.selectedFilter = true;
|
||||||
|
this.create.filterName = val;
|
||||||
|
this.create.filterClass = val;
|
||||||
|
this.create.currentFilterName = val;
|
||||||
|
this.create.currentFilterClass = val;
|
||||||
|
this.filter.class = val;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMedia() {
|
||||||
|
this.mediaDrawer = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteMedia() {
|
||||||
|
let id = this.media[this.carouselCursor].id;
|
||||||
|
axios.delete('/api/v1/media', {
|
||||||
|
params: {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
if(this.media.length == 0) {
|
||||||
|
this.mediaDrawer = false;
|
||||||
|
}
|
||||||
|
this.ids.splice(this.carouselCursor, 1);
|
||||||
|
this.media.splice(this.carouselCursor, 1);
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Whoops!', 'An error occured when attempting to delete this, please try again', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
mediaAltText() {
|
||||||
|
swal({
|
||||||
|
text: 'Add a media description',
|
||||||
|
content: "input"
|
||||||
|
}).then(val => {
|
||||||
|
let media = this.media[this.carouselCursor];
|
||||||
|
media.alt = val;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
mediaLicense() {
|
||||||
|
swal({
|
||||||
|
text: 'Add a media license',
|
||||||
|
content: "input",
|
||||||
|
button: {
|
||||||
|
text: "Update",
|
||||||
|
closeModal: true,
|
||||||
|
},
|
||||||
|
}).then(val => {
|
||||||
|
let media = this.media[this.carouselCursor];
|
||||||
|
media.license = val;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
if(this.media.length == 0) {
|
||||||
|
swal('Whoops!', 'You need to add media before you can save this!', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = {
|
||||||
|
media: this.media,
|
||||||
|
caption: this.composeText,
|
||||||
|
visibility: this.visibility,
|
||||||
|
cw: this.nsfw
|
||||||
|
};
|
||||||
|
axios.post('/api/local/status/compose', data)
|
||||||
|
.then(res => {
|
||||||
|
let data = res.data;
|
||||||
|
window.location.href = data;
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit;
|
|
||||||
|
|
||||||
use Tests\TestCase;
|
|
||||||
use Illuminate\Foundation\Testing\WithFaker;
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
||||||
use App\Util\ActivityPub\Validator\Accept;
|
|
||||||
|
|
||||||
class AcceptVerbTest extends TestCase
|
|
||||||
{
|
|
||||||
protected $validAccept;
|
|
||||||
protected $invalidAccept;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
$this->validAccept = [
|
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
|
||||||
'id' => 'https://example.org/og/b3e4a40b-0b26-4c5a-9079-094bd633fab7',
|
|
||||||
'type' => 'Accept',
|
|
||||||
'actor' => 'https://example.org/u/alice',
|
|
||||||
'object' => [
|
|
||||||
'id' => 'https://example.net/u/bob#follows/bb27f601-ddb9-4567-8f16-023d90605ca9',
|
|
||||||
'type' => 'Follow',
|
|
||||||
'actor' => 'https://example.net/u/bob',
|
|
||||||
'object' => 'https://example.org/u/alice'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
$this->invalidAccept = [
|
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
|
||||||
'id' => 'https://example.org/og/b3e4a40b-0b26-4c5a-9079-094bd633fab7',
|
|
||||||
'type' => 'Accept2',
|
|
||||||
'actor' => 'https://example.org/u/alice',
|
|
||||||
'object' => [
|
|
||||||
'id' => 'https://example.net/u/bob#follows/bb27f601-ddb9-4567-8f16-023d90605ca9',
|
|
||||||
'type' => 'Follow',
|
|
||||||
'actor' => 'https://example.net/u/bob',
|
|
||||||
'object' => 'https://example.org/u/alice'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function basic_accept()
|
|
||||||
{
|
|
||||||
$this->assertTrue(Accept::validate($this->validAccept));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function invalid_accept()
|
|
||||||
{
|
|
||||||
$this->assertFalse(Accept::validate($this->invalidAccept));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use App\Util\HttpSignatures\HeaderList;
|
|
||||||
|
|
||||||
class HeaderListTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
public function testToString()
|
|
||||||
{
|
|
||||||
$hl = new HeaderList(['(request-target)', 'Date']);
|
|
||||||
$this->assertEquals('(request-target) date', $hl->string());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFromStringRoundTripNormalized()
|
|
||||||
{
|
|
||||||
$hl = HeaderList::fromString('(request-target) Accept');
|
|
||||||
$this->assertEquals('(request-target) accept', $hl->string());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use GuzzleHttp\Psr7\Request;
|
|
||||||
use App\Util\HttpSignatures\Context;
|
|
||||||
|
|
||||||
class HmacContextTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
private $context;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->noDigestContext = new Context([
|
|
||||||
'keys' => ['pda' => 'secret'],
|
|
||||||
'algorithm' => 'hmac-sha256',
|
|
||||||
'headers' => ['(request-target)', 'date'],
|
|
||||||
]);
|
|
||||||
$this->withDigestContext = new Context([
|
|
||||||
'keys' => ['pda' => 'secret'],
|
|
||||||
'algorithm' => 'hmac-sha256',
|
|
||||||
'headers' => ['(request-target)', 'date', 'digest'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSignerNoDigestAction()
|
|
||||||
{
|
|
||||||
$message = new Request('GET', '/path?query=123', ['date' => 'today', 'accept' => 'llamas']);
|
|
||||||
$message = $this->noDigestContext->signer()->sign($message);
|
|
||||||
|
|
||||||
$expectedString = implode(',', [
|
|
||||||
'keyId="pda"',
|
|
||||||
'algorithm="hmac-sha256"',
|
|
||||||
'headers="(request-target) date"',
|
|
||||||
'signature="SFlytCGpsqb/9qYaKCQklGDvwgmrwfIERFnwt+yqPJw="',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedString,
|
|
||||||
$message->getHeader('Signature')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'Signature '.$expectedString,
|
|
||||||
$message->getHeader('Authorization')[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSignerAddDigestToHeadersList()
|
|
||||||
{
|
|
||||||
$message = new Request(
|
|
||||||
'POST', '/path/to/things?query=123',
|
|
||||||
['date' => 'today', 'accept' => 'llamas'],
|
|
||||||
'Thing to POST');
|
|
||||||
$message = $this->noDigestContext->signer()->signWithDigest($message);
|
|
||||||
|
|
||||||
$expectedString = implode(',', [
|
|
||||||
'keyId="pda"',
|
|
||||||
'algorithm="hmac-sha256"',
|
|
||||||
'headers="(request-target) date digest"',
|
|
||||||
'signature="HH6R3OJmJbKUFqqL0tGVIIb7xi1WbbSh/HBXHUtLkUs="', ]);
|
|
||||||
$expectedDigestHeader =
|
|
||||||
'SHA-256=rEcNhYZoBKiR29D30w1JcgArNlF8rXIXf5MnIL/4kcc=';
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedString,
|
|
||||||
$message->getHeader('Signature')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedDigestHeader,
|
|
||||||
$message->getHeader('Digest')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'Signature '.$expectedString,
|
|
||||||
$message->getHeader('Authorization')[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSignerReplaceDigest()
|
|
||||||
{
|
|
||||||
$message = new Request(
|
|
||||||
'PUT', '/things/thething?query=123',
|
|
||||||
['date' => 'today',
|
|
||||||
'accept' => 'llamas',
|
|
||||||
'Digest' => 'SHA-256=E/P+4y4x6EySO9qNAjCtQKxVwE1xKsNI/k+cjK+vtLU=', ],
|
|
||||||
'Thing to PUT at /things/thething please...');
|
|
||||||
$message = $this->noDigestContext->signer()->signWithDigest($message);
|
|
||||||
|
|
||||||
$expectedString = implode(',', [
|
|
||||||
'keyId="pda"',
|
|
||||||
'algorithm="hmac-sha256"',
|
|
||||||
'headers="(request-target) date digest"',
|
|
||||||
'signature="Hyatt1lSR/4XLI9Gcx8XOEKiG8LVktH7Lfr+0tmhwRU="', ]);
|
|
||||||
$expectedDigestHeader =
|
|
||||||
'SHA-256=mulOx+77mQU1EbPET50SCGA4P/4bYxVCJA1pTwJsaMw=';
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedString,
|
|
||||||
$message->getHeader('Signature')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedDigestHeader,
|
|
||||||
$message->getHeader('Digest')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'Signature '.$expectedString,
|
|
||||||
$message->getHeader('Authorization')[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSignerNewDigestIsInHeaderList()
|
|
||||||
{
|
|
||||||
$message = new Request(
|
|
||||||
'POST', '/path?query=123',
|
|
||||||
['date' => 'today',
|
|
||||||
'accept' => 'llamas', ],
|
|
||||||
'Stuff that belongs in /path');
|
|
||||||
$message = $this->withDigestContext->signer()->signWithDigest($message);
|
|
||||||
|
|
||||||
$expectedString = implode(',', [
|
|
||||||
'keyId="pda"',
|
|
||||||
'algorithm="hmac-sha256"',
|
|
||||||
'headers="(request-target) date digest"',
|
|
||||||
'signature="p8gQHs59X2WzQLUecfmxm1YO0OBTCNKldRZZBQsepfk="', ]);
|
|
||||||
$expectedDigestHeader =
|
|
||||||
'SHA-256=jnSMEfBSum4Rh2k6/IVFyvLuQLmGYwMAGBS9WybyDqQ=';
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedString,
|
|
||||||
$message->getHeader('Signature')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedDigestHeader,
|
|
||||||
$message->getHeader('Digest')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'Signature '.$expectedString,
|
|
||||||
$message->getHeader('Authorization')[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSignerNewDigestWithoutBody()
|
|
||||||
{
|
|
||||||
$message = new Request(
|
|
||||||
'GET', '/path?query=123',
|
|
||||||
['date' => 'today',
|
|
||||||
'accept' => 'llamas', ]);
|
|
||||||
$message = $this->withDigestContext->signer()->signWithDigest($message);
|
|
||||||
|
|
||||||
$expectedString = implode(',', [
|
|
||||||
'keyId="pda"',
|
|
||||||
'algorithm="hmac-sha256"',
|
|
||||||
'headers="(request-target) date digest"',
|
|
||||||
'signature="7iFqqryI6I9opV/Zp3eEg6PDY1tKw/3GqioOM7ACHHA="', ]);
|
|
||||||
$zeroLengthStringDigest =
|
|
||||||
'SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=';
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedString,
|
|
||||||
$message->getHeader('Signature')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$zeroLengthStringDigest,
|
|
||||||
$message->getHeader('Digest')[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'Signature '.$expectedString,
|
|
||||||
$message->getHeader('Authorization')[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testVerifier()
|
|
||||||
{
|
|
||||||
$message = $this->noDigestContext->signer()->sign(new Request('GET', '/path?query=123', [
|
|
||||||
'Signature' => 'keyId="pda",algorithm="hmac-sha1",headers="date",signature="x"',
|
|
||||||
'Date' => 'x',
|
|
||||||
]));
|
|
||||||
|
|
||||||
// assert it works without errors; correctness of results tested elsewhere.
|
|
||||||
$this->assertTrue(is_bool($this->noDigestContext->verifier()->isValid($message)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use App\Util\HttpSignatures\KeyStore;
|
|
||||||
|
|
||||||
class KeyStoreHmacTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
public function testFetchHmacSuccess()
|
|
||||||
{
|
|
||||||
$ks = new KeyStore(['hmacsecret' => 'ThisIsASecretKey']);
|
|
||||||
$key = $ks->fetch('hmacsecret');
|
|
||||||
$this->assertEquals(['hmacsecret', 'ThisIsASecretKey', 'ThisIsASecretKey', 'secret'], [
|
|
||||||
$key->getId(), $key->getVerifyingKey(), $key->getSigningKey(), $key->getType(), ]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use App\Util\HttpSignatures\KeyStore;
|
|
||||||
use App\Util\HttpSignatures\Key;
|
|
||||||
use Tests\Unit\HttpSignatures\TestKeys;
|
|
||||||
|
|
||||||
class KeyStoreRsaTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
openssl_pkey_export(
|
|
||||||
openssl_pkey_get_private(TestKeys::rsaPrivateKey),
|
|
||||||
$this->testRsaPrivateKeyPEM
|
|
||||||
);
|
|
||||||
$this->testRsaPublicKeyPEM = openssl_pkey_get_details(
|
|
||||||
openssl_get_publickey(TestKeys::rsaPublicKey)
|
|
||||||
)['key'];
|
|
||||||
$this->testRsaCert = TestKeys::rsaCert;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testParseX509inObject()
|
|
||||||
{
|
|
||||||
$keySpec = ['rsaCert' => [TestKeys::rsaCert]];
|
|
||||||
$this->assertTrue(Key::hasX509Certificate($keySpec));
|
|
||||||
|
|
||||||
$ks = new KeyStore($keySpec);
|
|
||||||
$publicKey = $ks->fetch('rsaCert')->getVerifyingKey();
|
|
||||||
$this->assertEquals('asymmetric', $ks->fetch('rsaCert')->getType());
|
|
||||||
$this->assertEquals(TestKeys::rsaPublicKey, $publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testParseRsaPublicKeyinObject()
|
|
||||||
{
|
|
||||||
$keySpec = ['rsaPubKey' => [TestKeys::rsaPublicKey]];
|
|
||||||
$this->assertTrue(Key::hasPublicKey($keySpec));
|
|
||||||
|
|
||||||
$ks = new KeyStore($keySpec);
|
|
||||||
$publicKey = $ks->fetch('rsaPubKey')->getVerifyingKey();
|
|
||||||
$this->assertEquals('asymmetric', $ks->fetch('rsaPubKey')->getType());
|
|
||||||
$this->assertEquals(TestKeys::rsaPublicKey, $publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testParsePrivateKeyinObject()
|
|
||||||
{
|
|
||||||
$keySpec = ['rsaPrivKey' => [TestKeys::rsaPrivateKey]];
|
|
||||||
$this->assertTrue(Key::hasPrivateKey($keySpec));
|
|
||||||
|
|
||||||
$ks = new KeyStore($keySpec);
|
|
||||||
$publicKey = $ks->fetch('rsaPrivKey')->getSigningKey();
|
|
||||||
$this->assertEquals('asymmetric', $ks->fetch('rsaPrivKey')->getType());
|
|
||||||
$this->assertEquals($this->testRsaPrivateKeyPEM, $publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFetchRsaSigningKeySuccess()
|
|
||||||
{
|
|
||||||
$ks = new KeyStore(['rsakey' => TestKeys::rsaPrivateKey]);
|
|
||||||
$key = $ks->fetch('rsakey');
|
|
||||||
openssl_pkey_export($key->getSigningKey(), $keyStoreSigningKey);
|
|
||||||
$this->assertEquals(['rsakey', $this->testRsaPrivateKeyPEM, null, 'asymmetric'], [
|
|
||||||
$key->getId(), $keyStoreSigningKey, $key->getVerifyingKey(), $key->getType(), ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFetchRsaVerifyingKeyFromCertificateSuccess()
|
|
||||||
{
|
|
||||||
$ks = new KeyStore(['rsacert' => TestKeys::rsaCert]);
|
|
||||||
$key = $ks->fetch('rsacert');
|
|
||||||
$keyStoreVerifyingKey = $key->getVerifyingKey();
|
|
||||||
$this->assertEquals(['rsacert', null, $this->testRsaPublicKeyPEM, 'asymmetric'], [
|
|
||||||
$key->getId(), $key->getSigningKey(), $keyStoreVerifyingKey, $key->getType(), ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFetchRsaVerifyingKeyFromPublicKeySuccess()
|
|
||||||
{
|
|
||||||
$ks = new KeyStore(['rsapubkey' => TestKeys::rsaPublicKey]);
|
|
||||||
$key = $ks->fetch('rsapubkey');
|
|
||||||
$keyStoreVerifyingKey = $key->getVerifyingKey();
|
|
||||||
$this->assertEquals(['rsapubkey', null, $this->testRsaPublicKeyPEM, 'asymmetric'], [
|
|
||||||
$key->getId(), $key->getSigningKey(), $keyStoreVerifyingKey, $key->getType(), ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFetchRsaBothSuccess()
|
|
||||||
{
|
|
||||||
$ks = new KeyStore(['rsaboth' => [TestKeys::rsaCert, TestKeys::rsaPrivateKey]]);
|
|
||||||
$key = $ks->fetch('rsaboth');
|
|
||||||
$keyStoreVerifyingKey = $key->getVerifyingKey();
|
|
||||||
$keyStoreSigningKey = $key->getSigningKey();
|
|
||||||
$this->assertEquals(['rsaboth', $this->testRsaPrivateKeyPEM, $this->testRsaPublicKeyPEM, 'asymmetric'], [
|
|
||||||
$key->getId(), $keyStoreSigningKey, $keyStoreVerifyingKey, $key->getType(), ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFetchRsaBothSuccessSwitched()
|
|
||||||
{
|
|
||||||
$ks = new KeyStore(['rsabothswitch' => [TestKeys::rsaPrivateKey, TestKeys::rsaCert]]);
|
|
||||||
$key = $ks->fetch('rsabothswitch');
|
|
||||||
$keyStoreVerifyingKey = $key->getVerifyingKey();
|
|
||||||
$keyStoreSigningKey = $key->getSigningKey();
|
|
||||||
$this->assertEquals(['rsabothswitch', $this->testRsaPrivateKeyPEM, $this->testRsaPublicKeyPEM, 'asymmetric'], [
|
|
||||||
$key->getId(), $keyStoreSigningKey, $keyStoreVerifyingKey, $key->getType(), ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \App\Util\HttpSignatures\KeyException
|
|
||||||
*/
|
|
||||||
public function testRsaMismatch()
|
|
||||||
{
|
|
||||||
$privateKey = openssl_pkey_new([
|
|
||||||
'private_key_type' => 'OPENSSL_KEYTYPE_RSA',
|
|
||||||
'private_key_bits' => 1024, ]
|
|
||||||
);
|
|
||||||
$ks = new Key('badpki', [TestKeys::rsaCert, $privateKey]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use App\Util\HttpSignatures\KeyStore;
|
|
||||||
|
|
||||||
class KeyStoreTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @expectedException App\Util\HttpSignatures\Exception
|
|
||||||
*/
|
|
||||||
public function testFetchFail()
|
|
||||||
{
|
|
||||||
$ks = new KeyStore(['id' => 'secret']);
|
|
||||||
$key = $ks->fetch('nope');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use GuzzleHttp\Psr7\Request;
|
|
||||||
use App\Util\HttpSignatures\Context;
|
|
||||||
use Tests\Unit\HttpSignatures\TestKeys;
|
|
||||||
|
|
||||||
class RsaContextTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
private $context;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->sha1context = new Context([
|
|
||||||
'keys' => ['rsa1' => TestKeys::rsaPrivateKey],
|
|
||||||
'algorithm' => 'rsa-sha1',
|
|
||||||
'headers' => ['(request-target)', 'date'],
|
|
||||||
]);
|
|
||||||
$this->sha256context = new Context([
|
|
||||||
'keys' => ['rsa1' => TestKeys::rsaPrivateKey],
|
|
||||||
'algorithm' => 'rsa-sha256',
|
|
||||||
'headers' => ['(request-target)', 'date'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSha1Signer()
|
|
||||||
{
|
|
||||||
$message = new Request('GET', '/path?query=123', ['date' => 'today', 'accept' => 'llamas']);
|
|
||||||
|
|
||||||
$message = $this->sha1context->signer()->sign($message);
|
|
||||||
$expectedSha1String = implode(',', [
|
|
||||||
'keyId="rsa1"',
|
|
||||||
'algorithm="rsa-sha1"',
|
|
||||||
'headers="(request-target) date"',
|
|
||||||
'signature="YIR3DteE3Jmz1VAnUMTgjTn3vTKfQuZl1CJhMBvGOZpnzwKeYBXA'.
|
|
||||||
'H108FojnbSeVG/AXq9pcrA6AFK0peg0aueqxpaFlo+4L/q5XzJ+QoryY3dlSr'.
|
|
||||||
'xwVnE5s5M19xmFm/6YkZR/KPeANCsG4SPL82Um/PCEMU0tmKd6sSx+IIzAYbX'.
|
|
||||||
'G/VrFMDeQAdXqpU1EhgxopKEAapN8rChb49+1JfR/RxlSKiLukJJ6auurm2zM'.
|
|
||||||
'n2D40fR1d2umA5LAO7vRt2iQwVbtwiFkVlRqkMvGftCNZByu8jJ6StI5H7Efu'.
|
|
||||||
'ANSHAZXKXWNH8yxpBUW/QCHCZjPd0ugM0QJJIc7i8JbGlA=="',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedSha1String,
|
|
||||||
$message->getHeader('Signature')[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSha256Signer()
|
|
||||||
{
|
|
||||||
$message = new Request('GET', '/path?query=123', ['date' => 'today', 'accept' => 'llamas']);
|
|
||||||
|
|
||||||
$message = $this->sha256context->signer()->sign($message);
|
|
||||||
$expectedSha256String = implode(',', [
|
|
||||||
'keyId="rsa1"',
|
|
||||||
'algorithm="rsa-sha256"',
|
|
||||||
'headers="(request-target) date"',
|
|
||||||
'signature="WGIegQCC3GEwxbkuXtq67CAqeDhkwblxAH2uoDx5kfWurhLRA5WB'.
|
|
||||||
'FDA/aktsZAjuUoimG1w4CGxSecziER1ez44PBlHP2fCW4ArLgnQgcjkdN2cOf/g'.
|
|
||||||
'j0OVL8s2usG4o4tud/+jjF3nxTxLl3HC+erBKsJakwXbw9kt4Cr028BToVfNXsW'.
|
|
||||||
'oMFpv0IjcgBH2V41AVlX/mYBMMJAihBCIcpgAcGrrxmG2gkfvSn09wtTttkGHft'.
|
|
||||||
'PIp3VpB53zbemlJS9Yw3tmmHr6cvWSXqQy/bTsEOoQJ2REfn5eiyzsJu3GiOpiI'.
|
|
||||||
'LK67i/WH9moltJtlfV57TV72cgYtjWa6yqhtFg=="',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$expectedSha256String,
|
|
||||||
$message->getHeader('Signature')[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException App\Util\HttpSignatures\AlgorithmException
|
|
||||||
*/
|
|
||||||
public function testRsaBadalgorithm()
|
|
||||||
{
|
|
||||||
$sha224context = new Context([
|
|
||||||
'keys' => ['rsa1' => TestKeys::rsaPrivateKey],
|
|
||||||
'algorithm' => 'rsa-sha224',
|
|
||||||
'headers' => ['(request-target)', 'date'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use GuzzleHttp\Psr7\Request;
|
|
||||||
use App\Util\HttpSignatures\KeyStore;
|
|
||||||
use App\Util\HttpSignatures\Verifier;
|
|
||||||
use Tests\Unit\HttpSignatures\TestKeys;
|
|
||||||
|
|
||||||
class VerifierRsaTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
const DATE = 'Fri, 01 Aug 2014 13:44:32 -0700';
|
|
||||||
const DATE_DIFFERENT = 'Fri, 01 Aug 2014 13:44:33 -0700';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Verifier
|
|
||||||
*/
|
|
||||||
private $verifier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Request
|
|
||||||
*/
|
|
||||||
private $message;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->setUpRsaVerifier();
|
|
||||||
|
|
||||||
$sha1SignatureHeader =
|
|
||||||
'keyId="rsa1",algorithm="rsa-sha1",headers="(request-target) date",'.
|
|
||||||
'signature="YIR3DteE3Jmz1VAnUMTgjTn3vTKfQuZl1CJhMBvGOZpnzwKeYBXAH10'.
|
|
||||||
'8FojnbSeVG/AXq9pcrA6AFK0peg0aueqxpaFlo+4L/q5XzJ+QoryY3dlSrxwVnE5s5'.
|
|
||||||
'M19xmFm/6YkZR/KPeANCsG4SPL82Um/PCEMU0tmKd6sSx+IIzAYbXG/VrFMDeQAdXq'.
|
|
||||||
'pU1EhgxopKEAapN8rChb49+1JfR/RxlSKiLukJJ6auurm2zMn2D40fR1d2umA5LAO7'.
|
|
||||||
'vRt2iQwVbtwiFkVlRqkMvGftCNZByu8jJ6StI5H7EfuANSHAZXKXWNH8yxpBUW/QCH'.
|
|
||||||
'CZjPd0ugM0QJJIc7i8JbGlA=="';
|
|
||||||
|
|
||||||
$this->sha1Message = new Request('GET', '/path?query=123', [
|
|
||||||
'Date' => 'today',
|
|
||||||
'Signature' => $sha1SignatureHeader,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$sha256SignatureHeader =
|
|
||||||
'keyId="rsa1",algorithm="rsa-sha256",headers="(request-target) date",'.
|
|
||||||
'signature="WGIegQCC3GEwxbkuXtq67CAqeDhkwblxAH2uoDx5kfWurhLRA5WBFDA/a'.
|
|
||||||
'ktsZAjuUoimG1w4CGxSecziER1ez44PBlHP2fCW4ArLgnQgcjkdN2cOf/gj0OVL8s2us'.
|
|
||||||
'G4o4tud/+jjF3nxTxLl3HC+erBKsJakwXbw9kt4Cr028BToVfNXsWoMFpv0IjcgBH2V4'.
|
|
||||||
'1AVlX/mYBMMJAihBCIcpgAcGrrxmG2gkfvSn09wtTttkGHftPIp3VpB53zbemlJS9Yw3'.
|
|
||||||
'tmmHr6cvWSXqQy/bTsEOoQJ2REfn5eiyzsJu3GiOpiILK67i/WH9moltJtlfV57TV72c'.
|
|
||||||
'gYtjWa6yqhtFg=="';
|
|
||||||
|
|
||||||
$this->sha256Message = new Request('GET', '/path?query=123', [
|
|
||||||
'Date' => 'today',
|
|
||||||
'Signature' => $sha256SignatureHeader,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setUpRsaVerifier()
|
|
||||||
{
|
|
||||||
$keyStore = new KeyStore(['rsa1' => TestKeys::rsaPublicKey]);
|
|
||||||
$this->verifier = new Verifier($keyStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testVerifyValidRsaMessage()
|
|
||||||
{
|
|
||||||
$this->assertTrue($this->verifier->isValid($this->sha1Message));
|
|
||||||
$this->assertTrue($this->verifier->isValid($this->sha256Message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testVerifyValidRsaMessageAuthorizationHeader()
|
|
||||||
{
|
|
||||||
$message = $this->sha1Message->withHeader(
|
|
||||||
'Authorization',
|
|
||||||
"Signature {$this->sha1Message->getHeader('Signature')[0]}");
|
|
||||||
$message = $this->sha1Message->withoutHeader('Signature');
|
|
||||||
|
|
||||||
$this->assertTrue($this->verifier->isValid($this->sha1Message));
|
|
||||||
|
|
||||||
$message = $this->sha256Message->withHeader(
|
|
||||||
'Authorization',
|
|
||||||
"Signature {$this->sha256Message->getHeader('Signature')[0]}");
|
|
||||||
$message = $this->sha256Message->withoutHeader('Signature');
|
|
||||||
|
|
||||||
$this->assertTrue($this->verifier->isValid($this->sha256Message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectTamperedRsaRequestMethod()
|
|
||||||
{
|
|
||||||
$message = $this->sha1Message->withMethod('POST');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
$message = $this->sha256Message->withMethod('POST');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectTamperedRsaDate()
|
|
||||||
{
|
|
||||||
$message = $this->sha1Message->withHeader('Date', self::DATE_DIFFERENT);
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
$message = $this->sha256Message->withHeader('Date', self::DATE_DIFFERENT);
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectTamperedRsaSignature()
|
|
||||||
{
|
|
||||||
$message = $this->sha1Message->withHeader(
|
|
||||||
'Signature',
|
|
||||||
preg_replace('/signature="/', 'signature="x', $this->sha1Message->getHeader('Signature')[0])
|
|
||||||
);
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
$message = $this->sha256Message->withHeader(
|
|
||||||
'Signature',
|
|
||||||
preg_replace('/signature="/', 'signature="x', $this->sha256Message->getHeader('Signature')[0])
|
|
||||||
);
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectRsaMessageWithoutSignatureHeader()
|
|
||||||
{
|
|
||||||
$message = $this->sha1Message->withoutHeader('Signature');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
$message = $this->sha256Message->withoutHeader('Signature');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectRsaMessageWithGarbageSignatureHeader()
|
|
||||||
{
|
|
||||||
$message = $this->sha1Message->withHeader('Signature', 'not="a",valid="signature"');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
$message = $this->sha256Message->withHeader('Signature', 'not="a",valid="signature"');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectRsaMessageWithPartialSignatureHeader()
|
|
||||||
{
|
|
||||||
$message = $this->sha1Message->withHeader('Signature', 'keyId="aa",algorithm="bb"');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
$message = $this->sha256Message->withHeader('Signature', 'keyId="aa",algorithm="bb"');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectsRsaMessageWithUnknownKeyId()
|
|
||||||
{
|
|
||||||
$keyStore = new KeyStore(['nope' => 'secret']);
|
|
||||||
$verifier = new Verifier($keyStore);
|
|
||||||
$this->assertFalse($verifier->isValid($this->sha1Message));
|
|
||||||
$this->assertFalse($verifier->isValid($this->sha256Message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectsRsaMessageMissingSignedHeaders()
|
|
||||||
{
|
|
||||||
$message = $this->sha1Message->withoutHeader('Date');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
$message = $this->sha256Message->withoutHeader('Date');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use App\Util\HttpSignatures\SignatureParametersParser;
|
|
||||||
|
|
||||||
class SignatureParametersParserTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
public function testParseReturnsExpectedAssociativeArray()
|
|
||||||
{
|
|
||||||
$parser = new SignatureParametersParser(
|
|
||||||
'keyId="example",algorithm="hmac-sha1",headers="(request-target) date",signature="b64"'
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
[
|
|
||||||
'keyId' => 'example',
|
|
||||||
'algorithm' => 'hmac-sha1',
|
|
||||||
'headers' => '(request-target) date',
|
|
||||||
'signature' => 'b64',
|
|
||||||
],
|
|
||||||
$parser->parse()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException App\Util\HttpSignatures\SignatureParseException
|
|
||||||
*/
|
|
||||||
public function testParseThrowsTypedException()
|
|
||||||
{
|
|
||||||
$parser = new SignatureParametersParser('nope');
|
|
||||||
$parser->parse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException App\Util\HttpSignatures\SignatureParseException
|
|
||||||
*/
|
|
||||||
public function testParseExceptionForMissingComponents()
|
|
||||||
{
|
|
||||||
$parser = new SignatureParametersParser(
|
|
||||||
'keyId="example",algorithm="hmac-sha1",headers="(request-target) date"'
|
|
||||||
);
|
|
||||||
$parser->parse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use App\Util\HttpSignatures\HeaderList;
|
|
||||||
use App\Util\HttpSignatures\HmacAlgorithm;
|
|
||||||
use App\Util\HttpSignatures\RsaAlgorithm;
|
|
||||||
use App\Util\HttpSignatures\Key;
|
|
||||||
use App\Util\HttpSignatures\SignatureParameters;
|
|
||||||
|
|
||||||
class SignatureParametersTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
public function testHmacToString()
|
|
||||||
{
|
|
||||||
$key = new Key('pda', 'secret');
|
|
||||||
$algorithm = new HmacAlgorithm('sha256');
|
|
||||||
$headerList = new HeaderList(['(request-target)', 'date']);
|
|
||||||
|
|
||||||
$signature = $this->getMockBuilder('HttpSignatures\Signature')
|
|
||||||
->disableOriginalConstructor()
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$signature
|
|
||||||
->expects($this->any())
|
|
||||||
->method('string')
|
|
||||||
->will($this->returnValue('thesignature'));
|
|
||||||
|
|
||||||
$sp = new SignatureParameters($key, $algorithm, $headerList, $signature);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'keyId="pda",algorithm="hmac-sha256",headers="(request-target) date",signature="dGhlc2lnbmF0dXJl"',
|
|
||||||
$sp->string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRsaToString()
|
|
||||||
{
|
|
||||||
$key = new Key('pda', TestKeys::rsaPrivateKey);
|
|
||||||
$algorithm = new RsaAlgorithm('sha256');
|
|
||||||
$headerList = new HeaderList(['(request-target)', 'date']);
|
|
||||||
|
|
||||||
$signature = $this->getMockBuilder('HttpSignatures\Signature')
|
|
||||||
->disableOriginalConstructor()
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$signature
|
|
||||||
->expects($this->any())
|
|
||||||
->method('string')
|
|
||||||
->will($this->returnValue('thesignature'));
|
|
||||||
|
|
||||||
$sp = new SignatureParameters($key, $algorithm, $headerList, $signature);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'keyId="pda",algorithm="rsa-sha256",headers="(request-target) date",signature="dGhlc2lnbmF0dXJl"',
|
|
||||||
$sp->string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use GuzzleHttp\Psr7\Request;
|
|
||||||
use App\Util\HttpSignatures\HeaderList;
|
|
||||||
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
|
|
||||||
use App\Util\HttpSignatures\SigningString;
|
|
||||||
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
|
|
||||||
|
|
||||||
class SigningStringTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
public function testWithoutQueryString()
|
|
||||||
{
|
|
||||||
$headerList = new HeaderList(['(request-target)']);
|
|
||||||
$ss = new SigningString($headerList, $this->message('/path'));
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
'(request-target): get /path',
|
|
||||||
$ss->string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSigningStringWithOrderedQueryParameters()
|
|
||||||
{
|
|
||||||
$headerList = new HeaderList(['(request-target)', 'date']);
|
|
||||||
$ss = new SigningString($headerList, $this->message('/path?a=antelope&z=zebra'));
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
"(request-target): get /path?a=antelope&z=zebra\ndate: Mon, 28 Jul 2014 15:39:13 -0700",
|
|
||||||
$ss->string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSigningStringWithUnorderedQueryParameters()
|
|
||||||
{
|
|
||||||
$headerList = new HeaderList(['(request-target)', 'date']);
|
|
||||||
$ss = new SigningString($headerList, $this->message('/path?z=zebra&a=antelope'));
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
"(request-target): get /path?z=zebra&a=antelope\ndate: Mon, 28 Jul 2014 15:39:13 -0700",
|
|
||||||
$ss->string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSigningStringWithOrderedQueryParametersSymfonyRequest()
|
|
||||||
{
|
|
||||||
$headerList = new HeaderList(['(request-target)', 'date']);
|
|
||||||
$ss = new SigningString($headerList, $this->symfonyMessage('/path?a=antelope&z=zebra'));
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
"(request-target): get /path?a=antelope&z=zebra\ndate: Mon, 28 Jul 2014 15:39:13 -0700",
|
|
||||||
$ss->string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSigningStringWithUnorderedQueryParametersSymfonyRequest()
|
|
||||||
{
|
|
||||||
$headerList = new HeaderList(['(request-target)', 'date']);
|
|
||||||
$ss = new SigningString($headerList, $this->symfonyMessage('/path?z=zebra&a=antelope'));
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
"(request-target): get /path?z=zebra&a=antelope\ndate: Mon, 28 Jul 2014 15:39:13 -0700",
|
|
||||||
$ss->string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException App\Util\HttpSignatures\Exception
|
|
||||||
*/
|
|
||||||
public function testSigningStringErrorForMissingHeader()
|
|
||||||
{
|
|
||||||
$headerList = new HeaderList(['nope']);
|
|
||||||
$ss = new SigningString($headerList, $this->message('/'));
|
|
||||||
$ss->string();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function message($path)
|
|
||||||
{
|
|
||||||
return new Request('GET', $path, ['date' => 'Mon, 28 Jul 2014 15:39:13 -0700']);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function symfonyMessage($path)
|
|
||||||
{
|
|
||||||
$symfonyRequest = SymfonyRequest::create($path, 'GET');
|
|
||||||
$symfonyRequest->headers->replace(['date' => 'Mon, 28 Jul 2014 15:39:13 -0700']);
|
|
||||||
|
|
||||||
$psr7Factory = new DiactorosFactory();
|
|
||||||
$psrRequest = $psr7Factory->createRequest($symfonyRequest)->withRequestTarget($symfonyRequest->getRequestUri());
|
|
||||||
|
|
||||||
return $psrRequest;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
abstract class TestKeys
|
|
||||||
{
|
|
||||||
const rsaPrivateKey =
|
|
||||||
'-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEAxyfPtnW/CakdLpZVh90sR+hpRwiBBdS/za6wal0pFddl2xcL
|
|
||||||
sHY/IA1u348xOc4LEY2D0807o1IXSEaqrt9eUkAh+yLGKgDCWLGzW026HBMgojhn
|
|
||||||
Y702pmRI2CGqP0xtzrynCSmSMvhdKM9IMc7DGf7Wdo4Y0En0KPat+IyCZpbf7jUX
|
|
||||||
cGL/tjxMKS3fXNgthGWVDU2TBSQRFM+fgeH9egODd4DfpzfaUoUDcoB/CZ1FEa4+
|
|
||||||
wNfXR5CZ4WjIaeVbCQ09maup07J+KaGe8SQ9LjcUuityyuNBjy5nvQkbmDfAh082
|
|
||||||
2lZQYR62FM5EAlNptThODg6q4wkIQ+spLv1dSwIDAQABAoIBAQCXYQyCvVd7qV80
|
|
||||||
JTNYNWbONbuoMa+Y1hEA77LK9osfPf3/HbJV7FupKmzHY5lgPdyt9+pnWQ3m46Qs
|
|
||||||
3QIqMEEKthLeSJ1mGfOf5VrWoOtBIczhYYw9BPsAWSQBnP1CZf7lcQJqdX3aXmy5
|
|
||||||
c22F5oroPIuZzALSeBQt+utcDLml7dr4D2TbFkmh3ocGGK1SDUATGBVQG+MAVGFQ
|
|
||||||
J5BYLFPUp8QC2o1672/aqNkx1XO08vIoANtWz5Rjf2hCRrdw1liAN30nnZ+l9h3x
|
|
||||||
mbof0sUQMPxxmZ/u+HGPShkN+Y/DLfIyjHlpXI127WBwFLr5P/iL6+DDbsn4Iav3
|
|
||||||
L57kSxhhAoGBAO6v/ojuZFE0h3mKHy+SdWggcRqe8Re2C5QTODTy7hRwdUv791lk
|
|
||||||
jFnONqlcn51H/hDQwP8nnG8HFxJYl4IoCCdcWfeFlQ82oerIWmsX6H4BFBfF7I9Y
|
|
||||||
3Muo1BWj4vif/mwwsL0NOAn5XCO/Gi1nF7YZBd/or+X/x4XLSWKnBISRAoGBANWZ
|
|
||||||
xzhpUltGPfKWgfE+ETbZC++Nqsd1MHAdZOhOaeBY615DdmfVv7ryaTWJ1kRbRmq0
|
|
||||||
9eKmopEYqfCwyfKZ/8+2dudhndnnEqmiJPu2WFyEiR9sb8NMPcOOjKPhUMgP0ZNx
|
|
||||||
Ynz/oTvBOylvU2MfC0hpLghq50JeEJSiGxzE9kIbAoGAejcpeMnAGghwmd4Ma9pt
|
|
||||||
PXznDP93aXGwagiRTiNZnqOam+aPV3lxmAZL3NptbCZRxCBvwfZxVjRmLuGn6mA/
|
|
||||||
FJBoDKKcmWaa79HY4l8ij2pT9HxGzXttyuZOeiopbK7XomQoCxU6rXi+IhuW9sqD
|
|
||||||
zJzxch39+yHF8w8NK3Njj9ECgYEAj8ZXu5fhEIECV5SJWKmvipykFRXleyZdeUm/
|
|
||||||
z0Jgr9sKasO8In5U9PAQczIZYJ+TkWXHEE2bpVDVqqZE+KBB+T1XYb1qM+7+t+Hl
|
|
||||||
ROzjIzsu1VD3FZzvAf+kmPajmlZTegxa/8pNa9xQBz7hARo3TQFHM/FJQnnwbSuE
|
|
||||||
VmQZYjsCgYA9ADxvlgGQmo3uHup6u54S7MgwvzIK7WiXKkuoI5rp0B0mwTr3loVt
|
|
||||||
3r3tZBH4+z17fVhmoQ4a4kYT8ixn0XpaL0LOv8s02b36XCNlrfPlafOwhHfOHmlz
|
|
||||||
zQnzviLiUOgXyD8FwZlYx+hTM09CYPcdJWSPl6JVF7uxm2fX/HdS3w==
|
|
||||||
-----END RSA PRIVATE KEY-----';
|
|
||||||
const rsaCert =
|
|
||||||
'-----BEGIN CERTIFICATE-----
|
|
||||||
MIICmjCCAYICCQDIxrpvPCnqRjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0
|
|
||||||
ZXN0MB4XDTE4MDgxODEyMjMzMloXDTI4MDgxNTEyMjMzMlowDzENMAsGA1UEAwwE
|
|
||||||
dGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcnz7Z1vwmpHS6W
|
|
||||||
VYfdLEfoaUcIgQXUv82usGpdKRXXZdsXC7B2PyANbt+PMTnOCxGNg9PNO6NSF0hG
|
|
||||||
qq7fXlJAIfsixioAwlixs1tNuhwTIKI4Z2O9NqZkSNghqj9Mbc68pwkpkjL4XSjP
|
|
||||||
SDHOwxn+1naOGNBJ9Cj2rfiMgmaW3+41F3Bi/7Y8TCkt31zYLYRllQ1NkwUkERTP
|
|
||||||
n4Hh/XoDg3eA36c32lKFA3KAfwmdRRGuPsDX10eQmeFoyGnlWwkNPZmrqdOyfimh
|
|
||||||
nvEkPS43FLorcsrjQY8uZ70JG5g3wIdPNtpWUGEethTORAJTabU4Tg4OquMJCEPr
|
|
||||||
KS79XUsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAgnvP8UDFND0G/R5ptKeaAUvF
|
|
||||||
xmDWnyBOv8/GWb8i9zesBfgjmjoKfjIgbYS/z0ZqMHApuv6td6NovsCVgpfEpLAv
|
|
||||||
zxtljLtOWEeQ/25bespBiTiOVp1w8BzEZ2IhNX6M0LxXQkUXgeyOC2wnH6SH9rTW
|
|
||||||
USM0aZhhDcdOZ4q+OkpAN6uux3r0QNJLdU8vInBGoyE3s+7MjEun30HQy24HSgEA
|
|
||||||
p/Ee+dkqU2Jp7wr5omMzurGrEwre0KjNLbrDvcb/0u8r7RA5sghHiE7MUe8acGqR
|
|
||||||
GyMYMn7AX97SD2yxYgwt7i/v65wkAC5oxXA2Yg1TTJZrLD6obGv+wELnePhKgw==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
';
|
|
||||||
const rsaPublicKey =
|
|
||||||
'-----BEGIN PUBLIC KEY-----
|
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxyfPtnW/CakdLpZVh90s
|
|
||||||
R+hpRwiBBdS/za6wal0pFddl2xcLsHY/IA1u348xOc4LEY2D0807o1IXSEaqrt9e
|
|
||||||
UkAh+yLGKgDCWLGzW026HBMgojhnY702pmRI2CGqP0xtzrynCSmSMvhdKM9IMc7D
|
|
||||||
Gf7Wdo4Y0En0KPat+IyCZpbf7jUXcGL/tjxMKS3fXNgthGWVDU2TBSQRFM+fgeH9
|
|
||||||
egODd4DfpzfaUoUDcoB/CZ1FEa4+wNfXR5CZ4WjIaeVbCQ09maup07J+KaGe8SQ9
|
|
||||||
LjcUuityyuNBjy5nvQkbmDfAh0822lZQYR62FM5EAlNptThODg6q4wkIQ+spLv1d
|
|
||||||
SwIDAQAB
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
';
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\HttpSignatures;
|
|
||||||
|
|
||||||
use GuzzleHttp\Psr7\Request;
|
|
||||||
use App\Util\HttpSignatures\KeyStore;
|
|
||||||
use App\Util\HttpSignatures\Verifier;
|
|
||||||
|
|
||||||
class VerifierHmacTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
const DATE = 'Fri, 01 Aug 2014 13:44:32 -0700';
|
|
||||||
const DATE_DIFFERENT = 'Fri, 01 Aug 2014 13:44:33 -0700';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Verifier
|
|
||||||
*/
|
|
||||||
private $verifier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Request
|
|
||||||
*/
|
|
||||||
private $message;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->setUpHmacVerifier();
|
|
||||||
$this->setUpValidHmacMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setUpHmacVerifier()
|
|
||||||
{
|
|
||||||
$keyStore = new KeyStore(['secret1' => 'secret']);
|
|
||||||
$this->verifier = new Verifier($keyStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setUpValidHmacMessage()
|
|
||||||
{
|
|
||||||
$signatureHeader = sprintf(
|
|
||||||
'keyId="%s",algorithm="%s",headers="%s",signature="%s"',
|
|
||||||
'secret1',
|
|
||||||
'hmac-sha256',
|
|
||||||
'(request-target) date',
|
|
||||||
'cS2VvndvReuTLy52Ggi4j6UaDqGm9hMb4z0xJZ6adqU='
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->message = new Request('GET', '/path?query=123', [
|
|
||||||
'Date' => self::DATE,
|
|
||||||
'Signature' => $signatureHeader,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testVerifyValidHmacMessage()
|
|
||||||
{
|
|
||||||
$this->assertTrue($this->verifier->isValid($this->message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testVerifyValidHmacMessageAuthorizationHeader()
|
|
||||||
{
|
|
||||||
$message = $this->message->withHeader('Authorization', "Signature {$this->message->getHeader('Signature')[0]}");
|
|
||||||
$message = $message->withoutHeader('Signature');
|
|
||||||
|
|
||||||
$this->assertTrue($this->verifier->isValid($this->message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectTamperedHmacRequestMethod()
|
|
||||||
{
|
|
||||||
$message = $this->message->withMethod('POST');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectTamperedHmacDate()
|
|
||||||
{
|
|
||||||
$message = $this->message->withHeader('Date', self::DATE_DIFFERENT);
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectTamperedHmacSignature()
|
|
||||||
{
|
|
||||||
$message = $this->message->withHeader(
|
|
||||||
'Signature',
|
|
||||||
preg_replace('/signature="/', 'signature="x', $this->message->getHeader('Signature')[0])
|
|
||||||
);
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectHmacMessageWithoutSignatureHeader()
|
|
||||||
{
|
|
||||||
$message = $this->message->withoutHeader('Signature');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectHmacMessageWithGarbageSignatureHeader()
|
|
||||||
{
|
|
||||||
$message = $this->message->withHeader('Signature', 'not="a",valid="signature"');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectHmacMessageWithPartialSignatureHeader()
|
|
||||||
{
|
|
||||||
$message = $this->message->withHeader('Signature', 'keyId="aa",algorithm="bb"');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectsHmacMessageWithUnknownKeyId()
|
|
||||||
{
|
|
||||||
$keyStore = new KeyStore(['nope' => 'secret']);
|
|
||||||
$verifier = new Verifier($keyStore);
|
|
||||||
$this->assertFalse($verifier->isValid($this->message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRejectsHmacMessageMissingSignedHeaders()
|
|
||||||
{
|
|
||||||
$message = $this->message->withoutHeader('Date');
|
|
||||||
$this->assertFalse($this->verifier->isValid($message));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue