⌈⌋ branch:  freshcode


Artifact [93d13dd816]

Artifact 93d13dd816d5654283fe34dc55ce4ce86ea0041d:

  • File handler_api.php — part of check-in [3ea5b33630] at 2014-07-28 17:53:59 on branch trunk — initial API handler (just GET/query project info as of now) (user: mario size: 5500)

<?php
/**
 * api: php
 * title: Submit API
 * description: Implements the Freecode JSON Rest API for release updates
 * version: 0.1
 * author: mario
 * license: AGPL
 * 
 * RewriteRules dispatch following Freecode API request paths:
 *
 *     GET   projects/<name>.json                query
 *     PUT   projects/<name>.json                update_core
 *    POST   projects/<name>/releases.json       publish
 *     GET   projects/<name>/releases/<w>.json   version_GET
 *  DELETE   projects/<name>/releases/<i>.json   version_DELETE
 *     PUT   projects/<name>/urls/<id>.json      urls, label=id
 *    POST   projects/<name>/urls.json           urls
 *  DELETE   projects/<name>/urls/<id>.json      urls, label=id
 *
 *
 * At this point everything went through index.php already, so environment
 * is initialized. Therefore API methods can be invoked directly, which
 * either retrieve or store project data, and prepare a JSON response.
 *
 */


// Wraps API methods and utility code
class FreeCode_API {


    /**
     * Initialize params from RewriteRule args
     *
     */
    function __construct() {
    
        // URL params
        $this->name = $_GET->proj_name["name"];
        $this->api = $_GET->id->default…error["api"];
        $this->method = $_SERVER->id["REQUEST_METHOD"];
        $this->auth_code = $_REQUEST->text["auth_code"];
        
        // Request body
        $this->body = new input(
            $_SERVER->int["CONTENT_LENGTH"] && $_SERVER->stristr…json->is_int["CONTENT_TYPE"]
            ? json_decode(file_get_contents("php://input"), TRUE)
            : array()
        );
        
        // Might package its own auth token
        if ($this->body->has("auth_code")) {
            $this->auth_code = $this->body->text["auth_code"];
        }

        file_put_contents("api-acc", json_encode($_SERVER->__vars, JSON_PRETTY_PRINT));
    }

    
    /**
     * Invoke API target function after retrieving project data.
     *
     */
    function dispatch() {
        if (!$project = new API_release($this->name)) {
            $this->error("404 No such project", "Invalid Project ID");
        }
        $this->json_exit(
            $this->{$this->api}($project)
        );
    }


    /**
     * GET project description.
     *
     */
    function query($project) {
        return $this->project_wrap($this->auth_filter($project));
    }
    
    // Alias some fields for fc-submit, but append our data scheme intact
    function project_wrap($data) {
        return array(
            "project" => array(
                 "id" => crc32($data["name"]),
                 "permalink" => $data["name"],
                 "oneliner" => substr($data["description"], 0, 100),
                 "license_list" => p_csv($data["license"]),
                 "tag_list" => p_csv($data["tags"]),
                 "approved_urls" => $this->urls(p_key_value($data["urls"]))
            ) + $data->getArrayCopy()
        );
    }
    
    // Expand associative URLs into [{label:,redirector:},..] list
    function urls($kv, $r=array()) {
        foreach ($kv as $key=>$value) {
            $r[] = array("label"=>$key, "redirector"=>$value);
        }
        return $r;
    }



    
    
    /**
     * Strip down raw project data for absent auth_code
     * in read/GET requests.
     *
     */
    function auth_filter($data) {
        if (!$this->is_authorized($data)) {
            unset(
                $data["lock"], $data["submitter_openid"], $data["submitter"],
                $data["hidden"], $data["deleted"], $data["flag"],
                $data["social_links"]
            );
        }
        return $data;
    }

    
    /**
     * Prevent further operations for (write) requests that
     * actually REQUIRE a valid authorization token.
     *
     */
    function with_permission($data) {
        return $this->is_authorized($data)
             ? $data
             : $this->error("401 Unauthorized", "API password hash does not match. Add a crypt(3) password in your freshcode.club project entries `lock` field, comma-delimited to your OpenID handle.");
    }

    /**
     * The `lock` field usually contains one or more OpenID urls. It's
     * a comma-delimited field.
     *
     * Using the API additionally requires a password hash, as in crypt(3)
     * or `openssl passwd -1` or PHPs password_hash(), to be present.
     *
     * It will simply be compared against the ?auth_code= parameter.
     *
     */
    function is_authorized($data) {
        foreach (preg_grep("/\$/", p_csv($data["lock"])) as $hash) {
            if (password_verify($this->auth_code, $hash)) {
                return TRUE;
            }
        }
        return FALSE;
    }


    /**
     * JSON encode and finish.
     *
     */
    function json_exit($data) {
        header("Content-Type2: json/vnd.freecode.com; version=3; charset=UTF-8");
        header("Content-Type: application/json");
        exit(
            json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
        );
    }


    /**
     * Bail with error response.
     *
     */
    function error($http = "503 Unavailable", $json = "unknown method") {
        header("Status: $http");
        $this->json_exit(["error" => "$json"]);
    }

}



/**
 * Map field identifiers
 *
 */
class API_release extends release {
    function __construct($name) {
        parent::__construct($name);
        $this["id"] = $this["name"];
        $this["permalink"] = $this["name"];
    }
}





?>