From 2cbdbf38136bb597edd15835ddf539f77051a328 Mon Sep 17 00:00:00 2001 From: Frank Schubert Date: Wed, 14 Sep 2022 16:52:00 +0200 Subject: [PATCH] WIP Preorder 2022-09-14 --- Layout/default/Preordercampaign/Form.php | 2 +- application/Api/v1/AddressdbApicontroller.php | 19 +++ application/Api/v1/PreorderApicontroller.php | 157 ++++++++++++++++++ lib/mvcfronk/mfBase/mfBaseApicontroller.php | 90 +++++++++- public/.htaccess | 8 +- public/docs/.htaccess | 2 +- 6 files changed, 264 insertions(+), 14 deletions(-) create mode 100644 application/Api/v1/PreorderApicontroller.php diff --git a/Layout/default/Preordercampaign/Form.php b/Layout/default/Preordercampaign/Form.php index 3ac439e40..4a06e7d07 100644 --- a/Layout/default/Preordercampaign/Form.php +++ b/Layout/default/Preordercampaign/Form.php @@ -153,7 +153,7 @@
- Website Url oder Hostname; ein Eintrag pro Zeile + Hostname der Website, mit oder ohne Protokoll (https://); *. als Wildcard erlaubt (*.domain.com); ein Eintrag pro Zeile
diff --git a/application/Api/v1/AddressdbApicontroller.php b/application/Api/v1/AddressdbApicontroller.php index 8a9d782ce..fb4a3e4ec 100644 --- a/application/Api/v1/AddressdbApicontroller.php +++ b/application/Api/v1/AddressdbApicontroller.php @@ -1,6 +1,7 @@ db(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME); @@ -9,6 +10,24 @@ class AddressdbApicontroller extends mfBaseApicontroller { $this->addRoute("/addressdb/findStreet", "findStreet", "POST"); $this->addRoute("/addressdb/findZip", "findZip", "POST"); $this->addRoute("/addressdb/findCity", "findCity", "POST"); + + $this->allowMissingOrigin = false; + } + + protected function authenticated() { + $campaignApiuser = PreordercampaignApiuserModel::getFirst(["worker_id" => $this->me->id]); + $campaign = new Preordercampaign($campaignApiuser->preordercampaign_id); + if($campaign) { + foreach(PreordercampaignGemeindeModel::search(['preordercampaign_id' => $campaign->id]) as $gemeinde) { + $this->filter_gemeinde_ids[] = $gemeinde->id; + } + } + + foreach(PreordercampaignOriginhostnameModel::search(['preordercampaign_id' => $campaign->id]) as $origin) { + $this->addAllowedOrigin($origin->hostname); + } + + //var_dump($campaign, $this->allowed_origins);exit; } protected function findCity() { diff --git a/application/Api/v1/PreorderApicontroller.php b/application/Api/v1/PreorderApicontroller.php new file mode 100644 index 000000000..4c522fc31 --- /dev/null +++ b/application/Api/v1/PreorderApicontroller.php @@ -0,0 +1,157 @@ +db(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME); + + $this->addRoute("/preorder", "submitPreorder", "POST"); + + $this->allowMissingOrigin = false; + } + + protected function authenticated() { + $campaignApiuser = PreordercampaignApiuserModel::getFirst(["worker_id" => $this->me->id]); + $campaign = new Preordercampaign($campaignApiuser->preordercampaign_id); + if($campaign) { + $this->campaign = $campaign; + foreach(PreordercampaignGemeindeModel::search(['preordercampaign_id' => $campaign->id]) as $gemeinde) { + $this->filter_gemeinde_ids[] = $gemeinde->id; + } + } + + foreach(PreordercampaignOriginhostnameModel::search(['preordercampaign_id' => $campaign->id]) as $origin) { + $this->addAllowedOrigin($origin->hostname); + } + + //var_dump($campaign, $this->allowed_origins);exit; + } + + protected function submitPreorder() { + if(!$this->campaign) { + $this->log->debug("disallowed request because no campaign for apikey"); + return mfResponse::Forbidden(); + } + + $type = $this->post['type']; + if($type != "provision" && $type != "order") { + return mfResponse::BadRequest(["message" => "Unknown type"]); + } + + if(!array_key_exists("address", $this->post)) { + return mfResponse::BadRequest(['message' => "address missing"]); + } + + if(!array_key_exists("customer", $this->post)) { + return mfResponse::BadRequest(['message' => "customer data missing"]); + } + + // check address + if(!property_exists($this->post['address'],"street") || + !property_exists($this->post['address'],"housenumber") || + !property_exists($this->post['address'],"zip") || + !property_exists($this->post['address'],"city") + ) { + return mfResponse::BadRequest(['message' => "Mandatory address fields missing"]); + } + + $address_search = []; + foreach(['street' => 'strasse','housenumber' => "hausnummer",'zip' => "plz",'city' => "ortschaft"] as $key => $field_name) { + if(property_exists($this->post['address'], $key)) { + $address_search[$field_name] = trim($this->post['address']->$key); + } + } + + $unit_search = []; + foreach(['block','stiege','stock','tuer'] as $key) { + if(property_exists($this->post['address'], $key) && trim($this->post['address']->key)) { + $unit_search[$key] = trim($this->post['address']->$key); + } + } + + // check customer + + $customer = $this->post['customer']; + + if(!property_exists($customer,"firstname") || + !property_exists($customer,"lastname") || + !property_exists($customer,"street") || + !property_exists($customer,"zip") || + !property_exists($customer,"city") + ) { + return mfResponse::BadRequest(['message' => "Mandatory customer fields missing"]); + } + + + // search address in AddressDB + + foreach($address_search as $field => $value) { + $where .= " AND `$field` = '$value'"; + } + + $sql = "SELECT * FROM view_hausnummer WHERE 1=1 $where"; + $res = $this->db()->query($sql); + if(!$this->db()->num_rows($res)) { + //var_dump($this->db()->num_rows($res), $this->db()->fetch_object($res)); + return mfResponse::NotFound(['message' => "Adresse nicht gefunden"]); + } + + $address = $this->db()->fetch_object($res); + + + + + // search wohneinheit + $unit = false; + if(count($unit_search)) { + foreach($unit_search as $field => $value) { + if($field == "stock" || $field == "stiege") continue; // only check for block and tuer + $where .= " AND `$field` = '$value'"; + } + + $sql = "SELECT * FROM view_wohneinheit WHERE 1=1 $where AND hausnummer_id=".$address->hausnummer_id; + $res = $this->db()->query($sql); + if(!$this->db()->num_rows($res)) { + return mfResponse::NotFound(['message' => "Wohneinheit nicht gefunden"]); + } + + $unit = $this->db()->fetch_object($res); + //var_dump($this->db()->num_rows($res), $this->db()->fetch_object($res)); + + } + + $preorder_data = []; + $preorder_data['preordercampaign_id'] = $this->campaign->id; + $preorder_data['type'] = $type; + $preorder_data['adb_hausnummer_id'] = $address->hausnummer_id; + + if($unit) { + $preorder_data['adb_wohneinheit_id'] = $unit->wohneinheit_id; + } + + if($type == "provision") { + $product = $this->campaign->setup_products['provision'][0]; + if($product) { + $preorder_data['setup_product_id'] = $product->id; + $preorder_data['price_setup'] = $product->price_setup; + } + } + + + + foreach(['company','uid','firstname','lastname','street','zip','city','phone','email'] as $key) { + if(property_exists($customer, $key)) { + $preorder_data[$key] = $customer->$key; + } + } + + var_dump($preorder_data);exit; + + + var_dump($this->post);exit; + } + +} \ No newline at end of file diff --git a/lib/mvcfronk/mfBase/mfBaseApicontroller.php b/lib/mvcfronk/mfBase/mfBaseApicontroller.php index 4924594d9..7030566e6 100644 --- a/lib/mvcfronk/mfBase/mfBaseApicontroller.php +++ b/lib/mvcfronk/mfBase/mfBaseApicontroller.php @@ -21,6 +21,8 @@ class mfBaseApicontroller { protected $get = []; protected $post = []; protected $format = "default"; + protected $allowed_origins = []; + protected $allowMissingOrigin = true; private $http_method; private $routes = []; @@ -39,10 +41,22 @@ class mfBaseApicontroller { if($this->requireAuth) { $this->authenticateUser(); + if(method_exists($this,"authenticated")) { + $this->authenticated(); + } + } + + // Apicontroller should add allowed hostnames with $this->addAllowedOrigin() + $this->createCorsHeaders(); + + // CORS preflight OPTIONS + if($this->http_method == "OPTIONS") { + // dont execute route, OPTIONS only requires CORS headers + $this->return(mfResponse::Ok()); } // route to action - $this->route = $params['apicall'].(($params['apiparams']) ? $params['apiparams'] : ""); + $this->route = $params['apicall'].((array_key_exists("apiparams", $params)) ? $params['apiparams'] : ""); $responseData = $this->runRoute($this->route); if(!$responseData) { @@ -104,13 +118,6 @@ class mfBaseApicontroller { $this->return(mfResponse::ImATeaPot()); } - // CORS preflight OPTIONS - // CORS headers must be correctly set in .htaccess or vhost config - if($this->http_method == "OPTIONS") { - // XXX TODO: api endpoint should decide if Origin should be allowed access - $this->return(mfResponse::Ok()); - } - // POST Request $post = []; if($this->http_method == "POST") { @@ -210,6 +217,67 @@ class mfBaseApicontroller { } + private function createCorsHeaders() { + header("Access-Control-Allow-Methods: GET,POST,OPTIONS"); + header("Access-Control-Allow-Headers: X-Api-Key"); + //var_dump($this->headers);exit; + if(!is_array($this->allowed_origins) || !count($this->allowed_origins)) { + return true; + } + if(!array_key_exists("origin", $this->headers)) { + if(!$this->allowMissingOrigin) { + $this->return(mfResponse::Forbidden()); + } + return true; + } + + $request_origin = ["proto" => false, "hostname" => ""]; + $m = []; + if(preg_match('#^(https?)://(.+)(:\d+)?$#i', $this->headers['origin'], $m)) { + $request_origin['proto'] = $m[1]; + $request_origin['hostname'] = $m[2]; + } + + //var_dump($request_origin);exit; + + foreach($this->allowed_origins as $origin) { + //echo $origin." -> ".$_SERVER["HTTP_HOST"]; + $proto = false; + $hostname = $origin; + + $m = []; + if(preg_match('#^(https?)://(.+)$#i', $origin, $m)) { + $proto = $m[1]; + $hostname = $m[2]; + } + + if(substr($hostname, 0, 2) == "*.") { + $hostname = str_replace("*.", ".*\\.", $hostname); + } + //var_dump($hostname);exit; + //if($hostname == $request_origin['hostname']) { + if(preg_match('/^'.$hostname.'$/', $request_origin['hostname'])) { + if($proto) { + if($proto == $request_origin['proto']) { + header("Access-Control-Allow-Origin: $proto://".$request_origin['hostname']); + return true; + } + } else { + header("Access-Control-Allow-Origin: ".$request_origin['proto']."://".$request_origin['hostname']); + return true; + } + } + } + + if(!$this->allowMissingOrigin) { + $this->return(mfResponse::Forbidden()); + exit; + } + + return true; + + } + protected function runRoute($params) { if(!is_array($this->routes) || !count($this->routes)) { return false; @@ -303,6 +371,12 @@ class mfBaseApicontroller { "action" => $action, "method" => $method ]; + return true; + } + + protected function addAllowedOrigin($origin) { + $this->allowed_origins[] = trim($origin); + return true; } public static function loadApiClass($name) { diff --git a/public/.htaccess b/public/.htaccess index f717870af..efdafabbc 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,7 +1,7 @@ -SetEnvIf Origin "^(https://docs.thetool.xinon.at|https://editor.swagger.io|.*abstellgleis.at)$" AccessControlAllowOrigin=$0 -Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin -Header add Access-Control-Allow-Methods: "GET,POST,OPTIONS" -Header add Access-Control-Allow-Headers: "X-Api-Key" +#SetEnvIf Origin "^(https://docs.thetool.xinon.at|https://editor.swagger.io|.*abstellgleis.at)$" AccessControlAllowOrigin=$0 +#Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin +#Header add Access-Control-Allow-Methods: "GET,POST,OPTIONS" +#Header add Access-Control-Allow-Headers: "X-Api-Key" RewriteEngine on diff --git a/public/docs/.htaccess b/public/docs/.htaccess index 3f0414a6f..b767cba79 100644 --- a/public/docs/.htaccess +++ b/public/docs/.htaccess @@ -3,4 +3,4 @@ Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessCo Header add Access-Control-Allow-Methods: "GET,POST,OPTIONS" Header add Access-Control-Allow-Headers: "X-Api-Key" -Header add Cach-control: "no-store, no-cache, must-revalidate" +Header add Cache-control: "no-store, no-cache, must-revalidate"