1<?php 2## 3## Copyright 2013-2018 Opera Software AS 4## 5## Licensed under the Apache License, Version 2.0 (the "License"); 6## you may not use this file except in compliance with the License. 7## You may obtain a copy of the License at 8## 9## http://www.apache.org/licenses/LICENSE-2.0 10## 11## Unless required by applicable law or agreed to in writing, software 12## distributed under the License is distributed on an "AS IS" BASIS, 13## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14## See the License for the specific language governing permissions and 15## limitations under the License. 16## 17 18if(isset($router->vars['objects'])) { 19 $api = new API; 20 switch($router->vars['objects']) { 21 case 'zones': 22 if(isset($router->vars['id'])) { 23 if(isset($router->vars['subobjects'])) { 24 switch($router->vars['subobjects']) { 25 case 'changes': 26 if(isset($router->vars['subid'])) { 27 $api->zone_change($router->vars['id'], $router->vars['subid']); 28 } else { 29 $api->zone_changes($router->vars['id']); 30 } 31 } 32 } else { 33 $api->zone($router->vars['id']); 34 } 35 } else { 36 $api->zones(); 37 } 38 default: 39 header('HTTP/1.1 404 Not found'); 40 } 41} else { 42 $content = new PageSection('apihelp'); 43 44 $page = new PageSection('base'); 45 $page->set('title', 'API documentation'); 46 $page->set('content', $content); 47 $page->set('alerts', array()); 48 echo $page->generate(); 49} 50 51class API { 52 public function options($options, $params = array()) { 53 if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' || !isset($options[$_SERVER['REQUEST_METHOD']])) { 54 if(!isset($options[$_SERVER['REQUEST_METHOD']])) { 55 header('HTTP/1.1 405 Method not allowed'); 56 } 57 $optionlist = array_keys($options); 58 $optionlist[] = 'OPTIONS'; 59 header('Allow: '.implode(', ', $optionlist)); 60 exit; 61 } else { 62 try { 63 call_user_func_array(array($this, $options[$_SERVER['REQUEST_METHOD']]), $params); 64 } catch(AccessDenied $e) { 65 header('HTTP/1.1 403 Forbidden'); 66 log_exception($e); 67 $this->output(['errors' => [['userMessage' => 'Access denied', 'code' => 3]]]); 68 } catch(InvalidJSON $e) { 69 header('HTTP/1.1 400 Bad request'); 70 log_exception($e); 71 $this->output(['errors' => [['userMessage' => 'Invalid JSON', 'internalMessage' => $e->getMessage(), 'code' => 1]]]); 72 } catch(BadData $e) { 73 header('HTTP/1.1 400 Bad request'); 74 log_exception($e); 75 $this->output(['errors' => [['userMessage' => 'Bad data', 'internalMessage' => $e->getMessage(), 'code' => 2]]]); 76 } catch(ResourceRecordInvalid $e) { 77 header('HTTP/1.1 400 Bad request'); 78 log_exception($e); 79 $this->output(['errors' => [['userMessage' => 'Invalid resource record', 'internalMessage' => $e->getMessage(), 'code' => 4]]]); 80 } catch(Exception $e) { 81 header('HTTP/1.1 500 Internal server error'); 82 log_exception($e); 83 $this->output(['errors' => [['userMessage' => 'Something went wrong (server fault)', 'code' => 0]]]); 84 } 85 } 86 } 87 88 public function zones() { 89 $this->options(array('GET' => 'list_zones')); 90 } 91 92 public function list_zones() { 93 global $active_user; 94 $zones = $active_user->list_accessible_zones(); 95 $list = array(); 96 foreach($zones as $zone) { 97 $item = new StdClass; 98 $item->name = $zone->name; 99 $item->serial = $zone->serial; 100 $list[] = $item; 101 } 102 $this->output($list); 103 } 104 105 public function zone($zone_name) { 106 $this->options(array('GET' => 'show_zone', 'PATCH' => 'update_zone_rrsets'), array($zone_name)); 107 } 108 109 public function show_zone($zone_name) { 110 global $zone_dir, $active_user; 111 $zone = $zone_dir->get_zone_by_name($zone_name); 112 if(!$active_user->admin && !$active_user->access_to($zone)) throw new AccessDenied; 113 $data = new StdClass; 114 $data->name = $zone->name; 115 $data->serial = $zone->serial; 116 $data->rrsets = array(); 117 foreach($zone->list_resource_record_sets() as $rrset) { 118 $rrs_data = new StdClass; 119 $rrs_data->name = DNSName::abbreviate($rrset->name, $zone->name); 120 $rrs_data->type = $rrset->type; 121 $rrs_data->ttl = DNSTime::abbreviate($rrset->ttl); 122 $rrs_data->records = array(); 123 foreach($rrset->list_resource_records() as $rr) { 124 $rr_data = new StdClass; 125 $rr_data->content = DNSContent::decode($rr->content, $rrset->type, $zone_name); 126 $rr_data->enabled = !$rr->disabled; 127 $rrs_data->records[] = $rr_data; 128 } 129 $rrs_data->comments = array(); 130 foreach($rrset->list_comments() as $comment) { 131 $comment_data = new StdClass; 132 $comment_data->content = $comment->content; 133 $comment_data->account = $comment->account; 134 $comment_data->modified_at = $comment->modified_at; 135 $rrs_data->comments[] = $comment_data; 136 } 137 $data->rrsets[] = $rrs_data; 138 } 139 $this->output($data); 140 } 141 142 public function update_zone_rrsets($zone_name) { 143 global $zone_dir, $active_user; 144 $zone = $zone_dir->get_zone_by_name($zone_name); 145 if(!$active_user->admin && !$active_user->access_to($zone)) throw new AccessDenied; 146 $json = file_get_contents('php://input'); 147 $zone->process_bulk_json_rrset_update($json); 148 $this->output(null); 149 } 150 151 public function zone_changes($zone_name) { 152 $this->options(array('GET' => 'list_zone_changes'), array($zone_name)); 153 } 154 155 public function list_zone_changes($zone_name) { 156 global $zone_dir, $active_user; 157 $zone = $zone_dir->get_zone_by_name($zone_name); 158 if(!$active_user->admin && !$active_user->access_to($zone)) throw new AccessDenied; 159 $changesets = $zone->list_changesets(); 160 $list = array(); 161 foreach($changesets as $changeset) { 162 $item = new StdClass; 163 $item->id = $changeset->id; 164 $item->author_uid = $changeset->author->uid; 165 $item->change_date = $changeset->change_date->format('c'); 166 $item->comment = $changeset->comment; 167 $item->deleted = $changeset->deleted; 168 $item->added = $changeset->added; 169 $list[] = $item; 170 } 171 $this->output($list); 172 } 173 174 public function zone_change($zone_name, $changeset_id) { 175 $this->options(array('GET' => 'show_zone_change'), array($zone_name, $changeset_id)); 176 } 177 178 public function show_zone_change($zone_name, $changeset_id) { 179 global $zone_dir, $active_user; 180 $zone = $zone_dir->get_zone_by_name($zone_name); 181 if(!$active_user->admin && !$active_user->access_to($zone)) throw new AccessDenied; 182 $changeset = $zone->get_changeset_by_id($changeset_id); 183 $data = new StdClass; 184 $data->id = $changeset->id; 185 $data->author_uid = $changeset->author->uid; 186 $data->change_date = $changeset->change_date->format('c'); 187 $data->comment = $changeset->comment; 188 $data->deleted = $changeset->deleted; 189 $data->added = $changeset->added; 190 $data->changes = array(); 191 foreach($changeset->list_changes() as $change) { 192 $c_data = new StdClass; 193 $states = array(); 194 $states['before'] = unserialize($change->before); 195 $states['after'] = unserialize($change->after); 196 foreach($states as $state => $rrset) { 197 if($rrset) { 198 $c_data->{$state} = new StdClass; 199 $c_data->{$state}->name = $rrset->name; 200 $c_data->{$state}->type = $rrset->type; 201 $c_data->{$state}->ttl = DNSTime::abbreviate($rrset->ttl); 202 $c_data->{$state}->rrs = array(); 203 $c_data->{$state}->comment = $rrset->merge_comment_text(); 204 $rrs = $rrset->list_resource_records(); 205 foreach($rrs as $rr) { 206 $rr_data = new StdClass; 207 $rr_data->content = DNSContent::decode($rr->content, $rr->type, $zone_name); 208 $rr_data->enabled = !$rr->disabled; 209 $c_data->{$state}->rrs[] = $rr_data; 210 } 211 } 212 } 213 $data->changes[] = $c_data; 214 } 215 $this->output($data); 216 } 217 218 public function created($url) { 219 header('HTTP/1.1 201 Created'); 220 header('Location: /api/v2'.$url); 221 exit; 222 } 223 224 private function output($data) { 225 header('Content-type: application/json'); 226 echo json_encode($data, JSON_PRETTY_PRINT); 227 exit; 228 } 229} 230