Merge branch 'dev'
This commit is contained in:
commit
549567a358
20 changed files with 25 additions and 596 deletions
|
@ -61,7 +61,7 @@ class Admin::GestionnairesController < AdminController
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_gestionnaire!
|
def assign_gestionnaire!
|
||||||
if current_administrateur.gestionnaires.include? @gestionnaire
|
if current_administrateur.gestionnaires.include?(@gestionnaire)
|
||||||
flash.alert = 'Instructeur déjà ajouté'
|
flash.alert = 'Instructeur déjà ajouté'
|
||||||
else
|
else
|
||||||
@gestionnaire.administrateurs.push current_administrateur
|
@gestionnaire.administrateurs.push current_administrateur
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Champs::CarteController < ApplicationController
|
||||||
coordinates = JSON.parse(coordinates)
|
coordinates = JSON.parse(coordinates)
|
||||||
|
|
||||||
if @champ.cadastres?
|
if @champ.cadastres?
|
||||||
cadastres = ModuleApiCartoService.generate_cadastre(coordinates)
|
cadastres = ApiCartoService.generate_cadastre(coordinates)
|
||||||
geo_areas += cadastres.map do |cadastre|
|
geo_areas += cadastres.map do |cadastre|
|
||||||
cadastre[:source] = GeoArea.sources.fetch(:cadastre)
|
cadastre[:source] = GeoArea.sources.fetch(:cadastre)
|
||||||
cadastre
|
cadastre
|
||||||
|
@ -49,7 +49,7 @@ class Champs::CarteController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
if @champ.quartiers_prioritaires?
|
if @champ.quartiers_prioritaires?
|
||||||
quartiers_prioritaires = ModuleApiCartoService.generate_qp(coordinates)
|
quartiers_prioritaires = ApiCartoService.generate_qp(coordinates)
|
||||||
geo_areas += quartiers_prioritaires.map do |qp|
|
geo_areas += quartiers_prioritaires.map do |qp|
|
||||||
qp[:source] = GeoArea.sources.fetch(:quartier_prioritaire)
|
qp[:source] = GeoArea.sources.fetch(:quartier_prioritaire)
|
||||||
qp
|
qp
|
||||||
|
@ -57,7 +57,7 @@ class Champs::CarteController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
if @champ.parcelles_agricoles?
|
if @champ.parcelles_agricoles?
|
||||||
parcelles_agricoles = ModuleApiCartoService.generate_rpg(coordinates)
|
parcelles_agricoles = ApiCartoService.generate_rpg(coordinates)
|
||||||
geo_areas += parcelles_agricoles.map do |parcelle_agricole|
|
geo_areas += parcelles_agricoles.map do |parcelle_agricole|
|
||||||
parcelle_agricole[:source] = GeoArea.sources.fetch(:parcelle_agricole)
|
parcelle_agricole[:source] = GeoArea.sources.fetch(:parcelle_agricole)
|
||||||
parcelle_agricole
|
parcelle_agricole
|
||||||
|
|
|
@ -41,7 +41,7 @@ class FileSizeValidator < ActiveModel::EachValidator
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
if !value.kind_of? CarrierWave::Uploader::Base
|
if !value.kind_of?(CarrierWave::Uploader::Base)
|
||||||
raise(ArgumentError, "A CarrierWave::Uploader::Base object was expected")
|
raise(ArgumentError, "A CarrierWave::Uploader::Base object was expected")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,9 @@ class User < ApplicationRecord
|
||||||
|
|
||||||
def self.find_for_france_connect(email, siret)
|
def self.find_for_france_connect(email, siret)
|
||||||
user = User.find_by(email: email)
|
user = User.find_by(email: email)
|
||||||
|
|
||||||
if user.nil?
|
if user.nil?
|
||||||
return User.create(email: email, password: Devise.friendly_token[0, 20], siret: siret)
|
User.create(email: email, password: Devise.friendly_token[0, 20], siret: siret)
|
||||||
else
|
else
|
||||||
user.update(siret: siret)
|
user.update(siret: siret)
|
||||||
user
|
user
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class ModuleApiCartoService
|
class ApiCartoService
|
||||||
def self.generate_qp(coordinates)
|
def self.generate_qp(coordinates)
|
||||||
coordinates.flat_map do |coordinate|
|
coordinates.flat_map do |coordinate|
|
||||||
ApiCarto::QuartiersPrioritairesAdapter.new(
|
ApiCarto::QuartiersPrioritairesAdapter.new(
|
|
@ -1,18 +1,14 @@
|
||||||
class ClamavService
|
class ClamavService
|
||||||
def self.safe_file?(file_path)
|
def self.safe_file?(file_path)
|
||||||
if Rails.env == 'development'
|
if Rails.env.development?
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
FileUtils.chmod 0666, file_path
|
FileUtils.chmod(0666, file_path)
|
||||||
|
|
||||||
client = ClamAV::Client.new
|
client = ClamAV::Client.new
|
||||||
response = client.execute(ClamAV::Commands::ScanCommand.new(file_path))
|
response = client.execute(ClamAV::Commands::ScanCommand.new(file_path))
|
||||||
|
|
||||||
if response.first.class == ClamAV::VirusResponse
|
response.first.class != ClamAV::VirusResponse
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,7 @@ class PiecesJustificativesService
|
||||||
|
|
||||||
def self.upload_one!(dossier, user, params)
|
def self.upload_one!(dossier, user, params)
|
||||||
content = params[:piece_justificative][:content]
|
content = params[:piece_justificative][:content]
|
||||||
if ClamavService.safe_file? content.path
|
if ClamavService.safe_file?(content.path)
|
||||||
pj = PieceJustificative.new(content: content,
|
pj = PieceJustificative.new(content: content,
|
||||||
dossier: dossier,
|
dossier: dossier,
|
||||||
type_de_piece_justificative: nil,
|
type_de_piece_justificative: nil,
|
||||||
|
|
|
@ -47,9 +47,9 @@
|
||||||
.description
|
.description
|
||||||
%h4 Refuser
|
%h4 Refuser
|
||||||
L'usager sera notifié que son dossier a été refusé
|
L'usager sera notifié que son dossier a été refusé
|
||||||
= render partial: 'new_gestionnaire/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Accepter le dossier', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" }
|
= render partial: 'new_gestionnaire/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Accepter le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" }
|
||||||
= render partial: 'new_gestionnaire/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Classer le dossier sans suite', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' }
|
= render partial: 'new_gestionnaire/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Classer le dossier sans suite', placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' }
|
||||||
= render partial: 'new_gestionnaire/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Refuser le dossier', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' }
|
= render partial: 'new_gestionnaire/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Refuser le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' }
|
||||||
|
|
||||||
- else
|
- else
|
||||||
- if dossier.motivation.present? || dossier.attestation.present?
|
- if dossier.motivation.present? || dossier.attestation.present?
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
= form_tag(terminer_gestionnaire_dossier_path(dossier.procedure, dossier), remote: true, method: :post, class: 'form') do
|
= form_tag(terminer_gestionnaire_dossier_path(dossier.procedure, dossier), remote: true, method: :post, class: 'form') do
|
||||||
- if title == 'Accepter'
|
- if title == 'Accepter'
|
||||||
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: 'Rédigez votre motivation ici (facultative)', required: false
|
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: placeholder, required: false
|
||||||
%p.help
|
%p.help
|
||||||
L'acceptation du dossier envoie automatiquement une attestation à l'usager.
|
L'acceptation du dossier envoie automatiquement une attestation à l'usager.
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
- unspecified_annotations_privees.each do |unspecified_annotations_privee|
|
- unspecified_annotations_privees.each do |unspecified_annotations_privee|
|
||||||
%li= unspecified_annotations_privee.libelle
|
%li= unspecified_annotations_privee.libelle
|
||||||
- else
|
- else
|
||||||
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: 'Rédigez votre motivation ici (obligatoire)', required: true
|
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: placeholder, required: true
|
||||||
.text-right
|
.text-right
|
||||||
%span.button{ onclick: 'DS.motivationCancel();' } Annuler
|
%span.button{ onclick: 'DS.motivationCancel();' } Annuler
|
||||||
= button_tag 'Valider la décision', name: :process_action, value: process_action, class: 'button primary', title: title, data: { confirm: confirm }
|
= button_tag 'Valider la décision', name: :process_action, value: process_action, class: 'button primary', title: title, data: { confirm: confirm }
|
||||||
|
|
|
@ -25,10 +25,6 @@ FOG_DIRECTORY=""
|
||||||
FOG_ENABLED=""
|
FOG_ENABLED=""
|
||||||
CARRIERWAVE_CACHE_DIR="/tmp/tps-local-cache"
|
CARRIERWAVE_CACHE_DIR="/tmp/tps-local-cache"
|
||||||
|
|
||||||
CLEVER_CLOUD_ACCESS_KEY_ID=""
|
|
||||||
CLEVER_CLOUD_SECRET_ACCESS_KEY=""
|
|
||||||
CLEVER_CLOUD_BUCKET=""
|
|
||||||
|
|
||||||
FC_PARTICULIER_ID=""
|
FC_PARTICULIER_ID=""
|
||||||
FC_PARTICULIER_SECRET=""
|
FC_PARTICULIER_SECRET=""
|
||||||
FC_PARTICULIER_BASE_URL=""
|
FC_PARTICULIER_BASE_URL=""
|
||||||
|
|
|
@ -4,11 +4,6 @@ local:
|
||||||
test:
|
test:
|
||||||
service: Disk
|
service: Disk
|
||||||
root: <%= Rails.root.join("tmp/storage") %>
|
root: <%= Rails.root.join("tmp/storage") %>
|
||||||
clever_cloud:
|
|
||||||
service: Cellar
|
|
||||||
access_key_id: <%= ENV['CLEVER_CLOUD_ACCESS_KEY_ID'] %>
|
|
||||||
secret_access_key: <%= ENV['CLEVER_CLOUD_SECRET_ACCESS_KEY'] %>
|
|
||||||
bucket: <%= ENV['CLEVER_CLOUD_BUCKET'] %>
|
|
||||||
openstack:
|
openstack:
|
||||||
service: OpenStack
|
service: OpenStack
|
||||||
container: "<%= ENV['FOG_ACTIVESTORAGE_DIRECTORY'] %>"
|
container: "<%= ENV['FOG_ACTIVESTORAGE_DIRECTORY'] %>"
|
||||||
|
|
6
db/migrate/20181218103212_drop_old_carto.rb
Normal file
6
db/migrate/20181218103212_drop_old_carto.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class DropOldCarto < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
drop_table :cadastres
|
||||||
|
drop_table :quartier_prioritaires
|
||||||
|
end
|
||||||
|
end
|
28
db/schema.rb
28
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2018_12_04_125101) do
|
ActiveRecord::Schema.define(version: 2018_12_18_103212) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -142,22 +142,6 @@ ActiveRecord::Schema.define(version: 2018_12_04_125101) do
|
||||||
t.index ["gestionnaire_id"], name: "index_avis_on_gestionnaire_id"
|
t.index ["gestionnaire_id"], name: "index_avis_on_gestionnaire_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "cadastres", id: :serial, force: :cascade do |t|
|
|
||||||
t.string "surface_intersection"
|
|
||||||
t.float "surface_parcelle"
|
|
||||||
t.string "numero"
|
|
||||||
t.integer "feuille"
|
|
||||||
t.string "section"
|
|
||||||
t.string "code_dep"
|
|
||||||
t.string "nom_com"
|
|
||||||
t.string "code_com"
|
|
||||||
t.string "code_arr"
|
|
||||||
t.text "geometry"
|
|
||||||
t.integer "dossier_id"
|
|
||||||
t.datetime "created_at"
|
|
||||||
t.datetime "updated_at"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "champs", id: :serial, force: :cascade do |t|
|
create_table "champs", id: :serial, force: :cascade do |t|
|
||||||
t.string "value"
|
t.string "value"
|
||||||
t.integer "type_de_champ_id"
|
t.integer "type_de_champ_id"
|
||||||
|
@ -487,16 +471,6 @@ ActiveRecord::Schema.define(version: 2018_12_04_125101) do
|
||||||
t.index ["service_id"], name: "index_procedures_on_service_id"
|
t.index ["service_id"], name: "index_procedures_on_service_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "quartier_prioritaires", id: :serial, force: :cascade do |t|
|
|
||||||
t.string "code"
|
|
||||||
t.string "nom"
|
|
||||||
t.string "commune"
|
|
||||||
t.text "geometry"
|
|
||||||
t.integer "dossier_id"
|
|
||||||
t.datetime "created_at"
|
|
||||||
t.datetime "updated_at"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "received_mails", id: :serial, force: :cascade do |t|
|
create_table "received_mails", id: :serial, force: :cascade do |t|
|
||||||
t.text "body"
|
t.text "body"
|
||||||
t.string "subject"
|
t.string "subject"
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
module ActiveStorage
|
|
||||||
class Service::CellarService < Service
|
|
||||||
def initialize(access_key_id:, secret_access_key:, bucket:, **)
|
|
||||||
@adapter = Cellar::CellarAdapter.new(access_key_id, secret_access_key, bucket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def upload(key, io, checksum: nil, **)
|
|
||||||
instrument :upload, key: key, checksum: checksum do
|
|
||||||
@adapter.session { |s| s.upload(key, io, checksum) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def download(key, &block)
|
|
||||||
if block_given?
|
|
||||||
instrument :streaming_download, key: key do
|
|
||||||
@adapter.session { |s| s.download(key, &block) }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
instrument :download, key: key do
|
|
||||||
@adapter.session { |s| s.download(key) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def download_chunk(key, range)
|
|
||||||
instrument :download_chunk, key: key, range: range do
|
|
||||||
@adapter.session { |s| s.download(key, range: range) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(key)
|
|
||||||
instrument :delete, key: key do
|
|
||||||
@adapter.session { |s| s.delete(key) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_prefixed(prefix)
|
|
||||||
instrument :delete_prefixed, prefix: prefix do
|
|
||||||
@adapter.session do |s|
|
|
||||||
keys = s.list_prefixed(prefix).map(&:first)
|
|
||||||
s.delete_keys(keys)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def exist?(key)
|
|
||||||
instrument :exist, key: key do |payload|
|
|
||||||
answer = @adapter.session { |s| s.exist?(key) }
|
|
||||||
payload[:exist] = answer
|
|
||||||
answer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def url(key, expires_in:, filename:, disposition:, content_type:)
|
|
||||||
instrument :url, key: key do |payload|
|
|
||||||
generated_url = @adapter.presigned_url(
|
|
||||||
method: 'GET',
|
|
||||||
key: key,
|
|
||||||
expires_in: expires_in,
|
|
||||||
"response-content-disposition": content_disposition_with(type: disposition, filename: filename),
|
|
||||||
"response-content-type": content_type
|
|
||||||
)
|
|
||||||
payload[:url] = generated_url
|
|
||||||
generated_url
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
|
||||||
instrument :url, key: key do |payload|
|
|
||||||
generated_url = @adapter.presigned_url(
|
|
||||||
method: 'PUT',
|
|
||||||
key: key,
|
|
||||||
expires_in: expires_in,
|
|
||||||
content_type: content_type,
|
|
||||||
checksum: checksum
|
|
||||||
)
|
|
||||||
payload[:url] = generated_url
|
|
||||||
generated_url
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def headers_for_direct_upload(key, content_type:, checksum:, **)
|
|
||||||
{ "Content-Type" => content_type, "Content-MD5" => checksum }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,43 +0,0 @@
|
||||||
require 'base64'
|
|
||||||
require 'openssl'
|
|
||||||
|
|
||||||
module Cellar
|
|
||||||
class AmazonV2RequestSigner
|
|
||||||
def initialize(access_key_id, secret_access_key, bucket)
|
|
||||||
@access_key_id = access_key_id
|
|
||||||
@secret_access_key = secret_access_key
|
|
||||||
@bucket = bucket
|
|
||||||
end
|
|
||||||
|
|
||||||
def sign(request, key)
|
|
||||||
date = Time.zone.now.httpdate
|
|
||||||
sig = signature(
|
|
||||||
method: request.method,
|
|
||||||
key: key,
|
|
||||||
date: date,
|
|
||||||
checksum: request['Content-MD5'] || '',
|
|
||||||
content_type: request.content_type || ''
|
|
||||||
)
|
|
||||||
request['date'] = date
|
|
||||||
request['authorization'] = "AWS #{@access_key_id}:#{sig}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_signature_params(method:, key:, expires_in:, content_type: '', checksum: '')
|
|
||||||
expires = expires_in.from_now.to_i
|
|
||||||
|
|
||||||
{
|
|
||||||
AWSAccessKeyId: @access_key_id,
|
|
||||||
Expires: expires,
|
|
||||||
Signature: signature(method: method, key: key, expires: expires, content_type: content_type, checksum: checksum)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def signature(method:, key:, expires: '', date: '', content_type: '', checksum: '')
|
|
||||||
canonicalized_amz_headers = ""
|
|
||||||
canonicalized_resource = "/#{@bucket}/#{key}"
|
|
||||||
string_to_sign = "#{method}\n#{checksum}\n#{content_type}\n#{expires}#{date}\n" +
|
|
||||||
"#{canonicalized_amz_headers}#{canonicalized_resource}"
|
|
||||||
Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), @secret_access_key, string_to_sign)).strip
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,183 +0,0 @@
|
||||||
require 'net/http'
|
|
||||||
require 'openssl'
|
|
||||||
|
|
||||||
module Cellar
|
|
||||||
class CellarAdapter
|
|
||||||
def initialize(access_key_id, secret_access_key, bucket)
|
|
||||||
@endpoint = URI::HTTPS.build(host: "#{bucket}.cellar.services.clever-cloud.com")
|
|
||||||
@signer = AmazonV2RequestSigner.new(access_key_id, secret_access_key, bucket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def presigned_url(method:, key:, expires_in:, content_type: '', checksum: '', **query_params)
|
|
||||||
query = query_params.merge(
|
|
||||||
@signer.url_signature_params(
|
|
||||||
method: method,
|
|
||||||
key: key,
|
|
||||||
expires_in: expires_in,
|
|
||||||
content_type: content_type,
|
|
||||||
checksum: checksum
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
URI::join(@endpoint, "/#{key}", "?#{query.to_query}").to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def session
|
|
||||||
Net::HTTP.start(@endpoint.host, @endpoint.port, use_ssl: true) do |http|
|
|
||||||
yield Session.new(http, @signer)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Session
|
|
||||||
def initialize(http, signer)
|
|
||||||
@http = http
|
|
||||||
@signer = signer
|
|
||||||
end
|
|
||||||
|
|
||||||
def upload(key, io, checksum)
|
|
||||||
with_io_length(io) do |io, length|
|
|
||||||
request = Net::HTTP::Put.new("/#{key}")
|
|
||||||
request.content_type = 'application/octet-stream'
|
|
||||||
request['Content-MD5'] = checksum
|
|
||||||
request['Content-Length'] = length
|
|
||||||
request.body_stream = io
|
|
||||||
@signer.sign(request, key)
|
|
||||||
@http.request(request)
|
|
||||||
# TODO: error handling
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def download(key, range: nil)
|
|
||||||
request = Net::HTTP::Get.new("/#{key}")
|
|
||||||
if range.present?
|
|
||||||
add_range_header(request, range)
|
|
||||||
end
|
|
||||||
@signer.sign(request, key)
|
|
||||||
if block_given?
|
|
||||||
@http.request(request) do |response|
|
|
||||||
if response.is_a?(Net::HTTPSuccess)
|
|
||||||
response.read_body do |chunk|
|
|
||||||
yield(chunk.force_encoding(Encoding::BINARY))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# TODO: error handling
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
response = @http.request(request)
|
|
||||||
if response.is_a?(Net::HTTPSuccess)
|
|
||||||
response.body.force_encoding(Encoding::BINARY)
|
|
||||||
else
|
|
||||||
# TODO: error handling
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(key)
|
|
||||||
# TODO: error handling
|
|
||||||
request = Net::HTTP::Delete.new("/#{key}")
|
|
||||||
@signer.sign(request, key)
|
|
||||||
@http.request(request)
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_prefixed(prefix)
|
|
||||||
result = []
|
|
||||||
marker = ''
|
|
||||||
|
|
||||||
begin
|
|
||||||
request = Net::HTTP::Get.new("/?prefix=#{prefix}&marker=#{marker}")
|
|
||||||
@signer.sign(request, "")
|
|
||||||
response = @http.request(request)
|
|
||||||
if response.is_a?(Net::HTTPSuccess)
|
|
||||||
(listing, truncated) = parse_bucket_listing(response.body)
|
|
||||||
result += listing
|
|
||||||
marker = listing.last.first
|
|
||||||
else
|
|
||||||
# TODO: error handling
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end while truncated
|
|
||||||
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_keys(keys)
|
|
||||||
request_body = bulk_deletion_request_body(keys)
|
|
||||||
request = Net::HTTP::Post.new("/?delete")
|
|
||||||
request.content_type = 'text/xml'
|
|
||||||
request['Content-MD5'] = Digest::MD5.base64digest(request_body)
|
|
||||||
request['Content-Length'] = request_body.length
|
|
||||||
request.body = request_body
|
|
||||||
@signer.sign(request, "?delete")
|
|
||||||
@http.request(request)
|
|
||||||
end
|
|
||||||
|
|
||||||
def exist?(key)
|
|
||||||
request = Net::HTTP::Head.new("/#{key}")
|
|
||||||
@signer.sign(request, key)
|
|
||||||
response = @http.request(request)
|
|
||||||
response.is_a?(Net::HTTPSuccess)
|
|
||||||
end
|
|
||||||
|
|
||||||
def last_modified(key)
|
|
||||||
request = Net::HTTP::Head.new("/#{key}")
|
|
||||||
@signer.sign(request, key)
|
|
||||||
response = @http.request(request)
|
|
||||||
if response.is_a?(Net::HTTPSuccess)
|
|
||||||
Time.zone.parse(response['Last-Modified'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def add_range_header(request, range)
|
|
||||||
bytes_end = range.exclude_end? ? range.end - 1 : range.end
|
|
||||||
|
|
||||||
request['range'] = "bytes=#{range.begin}-#{bytes_end}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_bucket_listing(bucket_listing_xml)
|
|
||||||
doc = Nokogiri::XML(bucket_listing_xml)
|
|
||||||
listing = doc
|
|
||||||
.xpath('//xmlns:Contents')
|
|
||||||
.map do |node|
|
|
||||||
[
|
|
||||||
node.xpath('xmlns:Key').text,
|
|
||||||
DateTime.iso8601(node.xpath('xmlns:LastModified').text)
|
|
||||||
]
|
|
||||||
end
|
|
||||||
truncated = doc.xpath('//xmlns:IsTruncated').text == 'true'
|
|
||||||
[listing, truncated]
|
|
||||||
end
|
|
||||||
|
|
||||||
def bulk_deletion_request_body(keys)
|
|
||||||
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
|
||||||
xml.Delete do
|
|
||||||
keys.each do |k|
|
|
||||||
xml.Object do
|
|
||||||
xml.Key(k)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
builder.to_xml
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_io_length(io)
|
|
||||||
if io.respond_to?(:size) && io.respond_to?(:pos)
|
|
||||||
yield(io, io.size - io.pos)
|
|
||||||
else
|
|
||||||
tmp_file = Tempfile.new('cellar_io_length')
|
|
||||||
begin
|
|
||||||
IO.copy_stream(io, tmp_file)
|
|
||||||
length = tmp_file.pos
|
|
||||||
tmp_file.rewind
|
|
||||||
yield(tmp_file, length)
|
|
||||||
ensure
|
|
||||||
tmp_file.close!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -32,15 +32,7 @@ namespace :'2018_12_03_finish_piece_jointe_transfer' do
|
||||||
end
|
end
|
||||||
|
|
||||||
def old_pj_adapter
|
def old_pj_adapter
|
||||||
if !defined? @old_pj_adapter
|
raise NotImplementedError, "No connection adapter for old PJ storage"
|
||||||
@old_pj_adapter = Cellar::CellarAdapter.new(
|
|
||||||
ENV['CLEVER_CLOUD_ACCESS_KEY_ID'],
|
|
||||||
ENV['CLEVER_CLOUD_SECRET_ACCESS_KEY'],
|
|
||||||
ENV['CLEVER_CLOUD_BUCKET']
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@old_pj_adapter
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_pjs
|
def new_pjs
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
require 'active_storage/service/cellar_service'
|
|
||||||
require 'cgi'
|
|
||||||
require 'net/http'
|
|
||||||
require 'uri'
|
|
||||||
|
|
||||||
describe 'CellarService' do
|
|
||||||
let(:cellar_service) do
|
|
||||||
# These are actual keys, but they’re safe to put here because
|
|
||||||
# - they never had any rights attached, and
|
|
||||||
# - the keys were revoked before copying them here
|
|
||||||
|
|
||||||
ActiveStorage::Service::CellarService.new(
|
|
||||||
access_key_id: 'AKIAJFTRSGRH3RXX6D5Q',
|
|
||||||
secret_access_key: '3/y/3Tf5zkfcrTaLFxyKB/oU2/7ay7/Dz8UdEHC7',
|
|
||||||
bucket: 'rogets'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
before { Timecop.freeze(Time.gm(2016, 10, 2)) }
|
|
||||||
after { Timecop.return }
|
|
||||||
|
|
||||||
describe 'presigned url for download' do
|
|
||||||
subject do
|
|
||||||
URI.parse(
|
|
||||||
cellar_service.url(
|
|
||||||
'fichier',
|
|
||||||
expires_in: 5.minutes,
|
|
||||||
filename: ActiveStorage::Filename.new("toto.png"),
|
|
||||||
disposition: 'attachment',
|
|
||||||
content_type: 'image/png'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
is_expected.to have_attributes(
|
|
||||||
scheme: 'https',
|
|
||||||
host: 'rogets.cellar.services.clever-cloud.com',
|
|
||||||
path: '/fichier'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
expect(CGI::parse(subject.query)).to eq(
|
|
||||||
{
|
|
||||||
'AWSAccessKeyId' => ['AKIAJFTRSGRH3RXX6D5Q'],
|
|
||||||
'Expires' => ['1475366700'],
|
|
||||||
'Signature' => ['nzCsB6cip8oofkuOdvvJs6FafkA='],
|
|
||||||
'response-content-disposition' => ["attachment; filename=\"toto.png\"; filename*=UTF-8''toto.png"],
|
|
||||||
'response-content-type' => ['image/png']
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'presigned url for direct upload' do
|
|
||||||
subject do
|
|
||||||
URI.parse(
|
|
||||||
cellar_service.url_for_direct_upload(
|
|
||||||
'fichier',
|
|
||||||
expires_in: 5.minutes,
|
|
||||||
content_type: 'image/png',
|
|
||||||
content_length: 2713,
|
|
||||||
checksum: 'DEADBEEF'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
is_expected.to have_attributes(
|
|
||||||
scheme: 'https',
|
|
||||||
host: 'rogets.cellar.services.clever-cloud.com',
|
|
||||||
path: '/fichier'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
expect(CGI::parse(subject.query)).to eq(
|
|
||||||
{
|
|
||||||
'AWSAccessKeyId' => ['AKIAJFTRSGRH3RXX6D5Q'],
|
|
||||||
'Expires' => ['1475366700'],
|
|
||||||
'Signature' => ['VwsX5nxGfTC3dxXjS6wSeU64r5o=']
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,43 +0,0 @@
|
||||||
require 'net/http'
|
|
||||||
|
|
||||||
describe 'AmazonV2RequestSigner' do
|
|
||||||
let(:request_signer) do
|
|
||||||
# These are actual keys, but they’re safe to put here because
|
|
||||||
# - they never had any rights attached, and
|
|
||||||
# - the keys were revoked before copying them here
|
|
||||||
|
|
||||||
Cellar::AmazonV2RequestSigner.new(
|
|
||||||
'AKIAJFTRSGRH3RXX6D5Q',
|
|
||||||
'3/y/3Tf5zkfcrTaLFxyKB/oU2/7ay7/Dz8UdEHC7',
|
|
||||||
'rogets'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
before { Timecop.freeze(Time.gm(2016, 10, 2)) }
|
|
||||||
after { Timecop.return }
|
|
||||||
|
|
||||||
describe 'signature generation' do
|
|
||||||
context 'for presigned URLs' do
|
|
||||||
subject do
|
|
||||||
request_signer.signature(
|
|
||||||
method: 'GET',
|
|
||||||
key: 'fichier',
|
|
||||||
expires: 5.minutes.from_now.to_i
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.to eq('nzCsB6cip8oofkuOdvvJs6FafkA=') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for server-side requests' do
|
|
||||||
subject do
|
|
||||||
Net::HTTP::Delete.new('https://rogets.cellar.services.clever-cloud.com/fichier')
|
|
||||||
end
|
|
||||||
|
|
||||||
before { request_signer.sign(subject, 'fichier') }
|
|
||||||
|
|
||||||
it { expect(subject['date']).to eq(Time.zone.now.httpdate) }
|
|
||||||
it { expect(subject['authorization']).to eq('AWS AKIAJFTRSGRH3RXX6D5Q:nkvviwZYb1V9HDrKyJZmY3Z8sSA=') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,89 +0,0 @@
|
||||||
describe 'CellarAdapter' do
|
|
||||||
let(:session) { Cellar::CellarAdapter::Session.new(nil, nil) }
|
|
||||||
|
|
||||||
before { Timecop.freeze(Time.gm(2016, 10, 2)) }
|
|
||||||
after { Timecop.return }
|
|
||||||
|
|
||||||
describe 'add_range_header' do
|
|
||||||
let(:request) { Net::HTTP::Get.new('/whatever') }
|
|
||||||
|
|
||||||
before { session.send(:add_range_header, request, range) }
|
|
||||||
|
|
||||||
subject { request['range'] }
|
|
||||||
|
|
||||||
context 'with end included' do
|
|
||||||
let(:range) { 100..500 }
|
|
||||||
|
|
||||||
it { is_expected.to eq('bytes=100-500') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with end excluded' do
|
|
||||||
let(:range) { 10...50 }
|
|
||||||
|
|
||||||
it { is_expected.to eq('bytes=10-49') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'parse_bucket_listing' do
|
|
||||||
let(:response) do
|
|
||||||
<<~EOS
|
|
||||||
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
||||||
<Name>example-bucket</Name>
|
|
||||||
<Prefix></Prefix>
|
|
||||||
<KeyCount>2</KeyCount>
|
|
||||||
<MaxKeys>1000</MaxKeys>
|
|
||||||
<Delimiter>/</Delimiter>
|
|
||||||
<IsTruncated>false</IsTruncated>
|
|
||||||
<Contents>
|
|
||||||
<Key>sample1.jpg</Key>
|
|
||||||
<LastModified>2011-02-26T01:56:20.000Z</LastModified>
|
|
||||||
<ETag>"bf1d737a4d46a19f3bced6905cc8b902"</ETag>
|
|
||||||
<Size>142863</Size>
|
|
||||||
<StorageClass>STANDARD</StorageClass>
|
|
||||||
</Contents>
|
|
||||||
<Contents>
|
|
||||||
<Key>sample2.jpg</Key>
|
|
||||||
<LastModified>2014-03-21T17:44:07.000Z</LastModified>
|
|
||||||
<ETag>"bf1d737a4d46a19f3bced6905cc8b902"</ETag>
|
|
||||||
<Size>142863</Size>
|
|
||||||
<StorageClass>STANDARD</StorageClass>
|
|
||||||
</Contents>
|
|
||||||
</ListBucketResult>'
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
subject { session.send(:parse_bucket_listing, response) }
|
|
||||||
|
|
||||||
it do
|
|
||||||
is_expected.to eq(
|
|
||||||
[
|
|
||||||
[
|
|
||||||
["sample1.jpg", DateTime.new(2011, 2, 26, 1, 56, 20, 0)],
|
|
||||||
["sample2.jpg", DateTime.new(2014, 3, 21, 17, 44, 7, 0)]
|
|
||||||
],
|
|
||||||
false
|
|
||||||
]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'bulk_deletion_request_body' do
|
|
||||||
let(:expected_response) do
|
|
||||||
<<~EOS
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Delete>
|
|
||||||
<Object>
|
|
||||||
<Key>chapi</Key>
|
|
||||||
</Object>
|
|
||||||
<Object>
|
|
||||||
<Key>chapo</Key>
|
|
||||||
</Object>
|
|
||||||
</Delete>
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
subject { session.send(:bulk_deletion_request_body, ['chapi', 'chapo']) }
|
|
||||||
|
|
||||||
it { is_expected.to eq(expected_response) }
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue