Download the PHP package 51degrees/fiftyone.pipeline.did without Composer

On this page you can find all versions of the php package 51degrees/fiftyone.pipeline.did. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package fiftyone.pipeline.did

51Degrees Pipeline 51Did (PHP)

Strongly typed PHP reader for the 51Did (51Degrees Identifier) returned by the 51Degrees Cloud service, and a client for the cloud's 51Did endpoints so a server never hand-writes HTTP or key handling. Mirrors the .NET FiftyOne.Did package. Composer package 51degrees/fiftyone.pipeline.did, namespace fiftyone\pipeline\did.

Terminology

Comparing two 51Dids means comparing their match keys, never their envelopes.

Payload layout

The byte structure of a 51Did is specified once for every language at identifier layout, which is the authority for what each byte holds, and what every 51Did package offers a caller is listed at package surface.

One byte after the match key holds the terms index, read through getTerms(), which answers with the address of the document the identifier was created under.

The package reads the payload for you and offers a named accessor for each field, so the offsets are not part of its surface. What a caller needs to know is that the flags byte names the identifier type, which decides the length of the match key that follows.

IdType Match key length Minimum payload
Probabilistic 32 37
Random 16 21
HashedEmail 32 37
Reserved remainder 5

Identifiers issued before the type tag existed decode as Probabilistic.

The minimum payload is the only length rule this package applies. There is no upper bound here, because anything after the terms byte is a creator context section whose lengths belong to the cloud, and an older reader has to keep accepting an identifier a newer cloud issues.

Terms

The terms byte says which terms document the identifier was created under, so the terms travel with the identifier rather than alongside it. It is an index into the table below and is not a version number, so that a later document can live at any address rather than only at one composed from a number. An index is never reused or repointed once published, because an identifier issued under it has to stay readable years later.

getTerms() answers with the address of the document. The package turns the index into the address, so a caller never handles the byte.

Index Document getTerms()
0 Not stated in the identifier null
1 Model Terms for Marketing, version 2 https://m4ow.uk/mtm/2.txt
other One this package cannot name null

An identifier whose payload ends at the match key carries no Terms byte, and it reads as index 0, so absence and a byte holding zero mean the same thing and no presence flag exists.

No address does not mean the identifier is unrestricted. It means only that this identifier does not carry the answer, so the answer has to come from the surrounding protocol, being the Terms Document Locator in an OpenRTB request or whatever else is provided. Carrying the terms here does not remove the need to carry that locator where a protocol has one, and where the two disagree, take the identifier's own value as the one that describes it, because it is inside the signature and the accompanying data is not.

An index added after this release also answers with no address, and this package never fetches an address and never builds one from an index it cannot name, because that would name a document nobody wrote and a receiver would record having accepted terms that do not exist. A caller therefore cannot tell an index of zero from an index this package cannot name, which is deliberate, since both lead to the same place.

The Reserved identifier type reads every byte after the header as the match key, so no byte is left to hold the terms and such an identifier answers with no address.

The payload version

Bits 4 and 5 of the flags byte say which payload layout the identifier follows, and this package reads version 0. A payload naming version 1, 2 or 3 is refused with FodIdParseStatus::UnsupportedPayloadVersion, and the raising readers name the version they found in the message.

No field is read under the layout this package knows once the version says otherwise. A later version exists precisely because a field moved, so reading such a payload here would answer with values that are wrong rather than absent, which is worse than refusing. A version that nothing checks protects nothing.

The version is not exposed. Either this package read the layout, in which case the accessors are the answer, or it did not, in which case there is no identifier to read fields from.

On an identifier carrying a creator context the four LicenseId bytes hold an encrypted value that only 51Degrees can turn back into a licence identifier, so getLicenseId() is the field's raw value and identifies nothing outside 51Degrees. Such an identifier also carries a context section after the match key, which the reader keeps in the payload and does not interpret.

Requirements & OWID dependency

PHP >= 8.1, because the OWID library requires it and 7.4 is end of life. FodId builds on the OWID envelope library (SWAN-community/owid-php, package swan-community/owid), taken from the 51Degrees/owid-php fork. Owid is final, so FodId composes it rather than subclassing.

That library is not on Packagist, so it reaches you one of two ways.

How a release is assembled covers how the published tree is built and why the two arrangements differ.

Install

Build from a checkout

Usage

FodId::fromBase64(), FodId::fromByteArray(), FodId::fromOwid() and the constructor remain and raise for the same inputs the try factories report, so code written against them keeps working.

What the identifier says it may be used for

getUsage() answers the usage the 51Did was created for, which decides where the identifier may go. One created for Usage::NonMarketing must never be passed to a demand source, and one created for Usage::Standard or Usage::Personalized may be passed only to a recipient that has accepted the applicable terms. Usage::None means no usage bit is set at all, which the cloud never issues, so such an identifier came from somewhere else or is damaged and should be treated as one that may not be passed on. idUsage() gives the cloud's id.usage value for the same answer, being non-marketing, standard or personalized.

Read the usage only through getUsage(). The three usages are cumulative in the flags byte rather than exclusive, as non-marketing sets bit 0, standard sets bits 0 and 1, and personalized sets bits 0, 1 and 2, so every marketing identifier also carries the non-marketing bit. Code that masked the byte for that bit alone would read every marketing identifier as non-marketing, which is the wrong way round for a data protection decision. getUsage() answers with the highest usage granted, so that mistake cannot be made, and this is why the raw flags byte, the byte offsets and the raw count of minutes in the date are no longer offered.

isUsageFromConsent() says whether the usage was worked out from an IAB consent string the caller sent rather than stated by the caller directly. Both are legitimate ways to arrive at a usage and the answer says nothing about which usage it is.

Reading versus verifying

Reading a 51Did and verifying one are separate questions with separate answers. Reading asks whether the bytes are a 51Did at all, and a successful read says nothing about the signature. A parsed 51Did is not necessarily genuine. Verifying asks whether the signature is genuine for a key, and only makes sense once there is a 51Did to ask about.

Reading

Every read through tryFromBase64(), tryFromByteArray() or tryFromOwid() reports the same three facts in a FodIdParseResult.

  1. $result->ok, whether the value was a 51Did.
  2. $result->fodId, the FodId on success and null otherwise, never a half read one.
  3. $result->status, ParseStatus::Parsed on success and the specific reason otherwise.

Reading is two steps and the status says which step failed. The OWID library reads the envelope first, and when the envelope cannot be read the result carries the library's own SwanCommunity\Owid\ParseStatus exactly as reported, with nothing mapped down to a generic value. Only when the envelope is sound does this package look inside the payload, and the two things the payload can get wrong are this package's own FodIdParseStatus.

Status From Meaning
Parsed OWID A 51Did in a sound envelope. Says nothing about the signature
MissingInput OWID Nothing was given, being null, an empty string or whitespace only
InvalidInputType OWID Not a string, for example the array PHP builds when a query parameter repeats with brackets
InvalidBase64 OWID The text is not base64, so there are no bytes to read
UnsupportedVersion OWID The first byte names an envelope version the library does not know
UnexpectedEnd OWID The bytes stop part way through a field, before the payload length was read
InvalidDomainEncoding OWID The creator domain has no terminator within the length a domain name can hold
ByteCountMismatch OWID The declared payload length disagrees with the bytes present, whichever way they fall short
AbsentNode OWID The marker for an absent OWID, a single zero byte, which is well formed and is not an identifier
PayloadTooShort this package The payload holds fewer than the 5 header bytes, so the type cannot be read
InvalidTypePayloadLength this package The header names a type whose match key the payload is too short to hold, being 16 bytes after the header for Random and 32 for Probabilistic or HashedEmail

Both enums are string backed with the cross language name of the status, so $result->status->value can be logged or carried between services whichever kind it is, and a status never carries the input that produced it.

Every one of these is an expected outcome of reading external data rather than a fault in the program, which is why the try factories report them. The exceptional cases are the ones that remain exceptions. A key that cannot be read, a cloud that cannot be reached, a key endpoint that answers with an error, and a transport that answers in the wrong shape are all faults in the operation rather than in the identifier, and verify() and DidClient raise for them as they always have.

Verifying

verify($publicKeyPem) answers true or false and raises an OwidException when the key itself cannot be read. Where the difference between a signature that does not match and a check that could not be made changes what your code does, ask signatureStatus($publicKeyPem) instead, which answers a SwanCommunity\Owid\SignatureStatus. SignatureStatus::SignatureInvalid is the only answer that means the identifier should be distrusted, and a key that cannot be read is SignatureStatus::InvalidKey, so an operational fault is never reported as a forgery. DidClient::verifySignature() fetches the published keys, and when the keys cannot be fetched it raises rather than answering false, for the same reason.

Before and after

A caller who reached the OWID library through this package before this release used its throwing factories or built an OWID directly. Both are gone from the library, and the package now reads like this.

FodId::fromBase64($value) still works and still raises OwidException when the envelope cannot be read (the message now names the library's status) and InvalidArgumentException when the envelope is sound and the payload does not fit, so a catch block written against either keeps working. An Owid reaches your code only from a successful read or from the library's Creator::create, so new Owid(...) and Owid::fromBase64() no longer exist to call.

Comparing two 51Dids

Use getMatchKey() as the cache and dedup key. The same match key means the same browser instance under the same usage policy on the same licence key (for idproblic) or across all callers (for idprobglobal).

Verifying on your server

DidClient handles every manipulation of a 51Did a server needs against the cloud. Build one at start-up with the page's resource key, the account's licence key (server side only, needed to redeem where the account holds licence keys) and, optionally, the API base. When the base is not given the client reads FOD_CLOUD_API_URL, the same variable the cloud request engine honours, and falls back to https://cloud.51degrees.com/api/v4/. A value with or without a trailing slash is accepted. Credentials never travel in a URL. The key list and verify calls carry the resource key in the route, and redeem is a POST whose form body carries the resource key, the identifier, the sealed result, the challenge and the licence key. A fourth constructor argument takes an HTTP transport callable so tests can run without the network.

redeem() returns a RedeemResult for a 200 and for a 503 (context Unconfirmed, so retry). It throws InvalidArgumentException with the cloud's message when the 51Did was malformed (400), NotSupportedException when the host does not offer the creator context (404), CloudException carrying the status and body for any other status, and RuntimeException when the cloud cannot be reached. A context string the package does not know maps to ContextOutcome::Unreadable and the raw value stays on rawContext. Every cryptographic failure comes back as the one word unreadable, by design, so the client does not try to distinguish them either.

verify() and redeem() also take the identifier as a string, in either alphabet. The client reads the string with FodId::tryFromBase64() first and refuses one that is not a 51Did with an InvalidArgumentException naming the status, before any key fetch or request, so a malformed value costs no use. A string that reads is sent exactly as given. Separately, and before the read, the client refuses any string longer than 4096 characters with the same exception type. That figure is client policy, deliberately arbitrary and generous, chosen so that obviously hostile input is dropped before it is even decoded, and it says nothing about how long a 51Did is or may be. It is not a limit of the format and the reader applies no such limit.

The verify-context and verify-full endpoints are browser calls rather than client methods, because the context describes the browser's own connection, and creation is the cloud json endpoint for the same reason. The demo below shows both.

Non-goals

Tests

The Live suite calls the cloud and is skipped unless _51DEGREES_RESOURCE_KEY (or the legacy RESOURCE_KEY) is set, with _51DEGREES_LICENSE_KEY and FOD_CLOUD_API_URL read as the demo reads them. Each live test costs uses against the subscription behind the key.

Creator context demo

Every 51Did the 51Degrees cloud issues carries a creator context, which binds the identifier to the browser and connection it was created on. The demo under examples/creator-context-web/ shows the flow against the cloud from a real browser, which is the only place the check makes sense, since a program verifying its own connection checks itself against itself. The demo's server uses DidClient from this package, so run composer install at the repository root first.

What the demo shows

The flow has three steps.

  1. Create a 51Did by loading the 51Degrees client script, which runs the snippets the service asks for, sends what they collected, and hands the page the answer with the identifier in it. The browser does all of that, so the identifier is created for the browser's own connection. The service issues an identifier only once those values are in, so a page asking for one by itself is told the page has not finished and is given nothing.
  2. Verify it with verify-full, which returns everything the cloud concluded, the signature outcome and the creator context verdict, only as an encrypted result that the caller cannot read or forge. The page sends what the snippets collected with this call as well, because the service compares this browser against the creator from those values and gives no verdict without them.
  3. Redeem the encrypted result with redeem, presenting the 51Did, the encrypted result and the account's licence key, and receive the signature outcome, the true creator context verdict, when the verification happened (verifiedAt) and how long ago that was (secondsSinceVerified).

In production step 2 runs in the visitor's browser (the page relays the encrypted result to your server) and step 3 runs on your server, which is the party holding the licence key. The demo passes a single-use challenge, binding the encrypted result to one transaction.

A verdict of nocontext is a normal outcome rather than an error, because a self-hosted service may be configured not to emit the creator context, so an identifier it issued has none to check. A 404 from verify-full or redeem means the host answering does not offer the creator context at all, which is a service without the feature rather than a failed check, so the page shows not supported by this host and the sentence The service at <api base> does not support the creator context. Point the demo at a service that does. Only a transport failure, another status outside the 2xx range, a body that is not JSON or an errors answer from the service is an error. The server relays the service's status and body to the page, which shows the failure naming the status and the start of the body.

examples/creator-context-web/ is a small demo web app, server.php serving page.html, that runs the flow the way production does. The browser creates the 51Did and calls verify-full, so the cloud observes the browser's live connection, then the page hands the encrypted result to its own server, which redeems it with the licence key. A fresh challenge is issued per page load and bound through both steps by the cloud. A production server would also remember the value it issued and reject a redemption carrying any other, which the demo keeps out of scope. The creation call requests every 51Did identifier in one request and the page shows all six in a table, the probabilistic pair (IdProbGlobal and IdProbLic) derived from the connection, the deterministic hashed-email pair (IdHemGlobal and IdHemLic) derived from the id.email evidence (the demo sends [email protected]), and the random pair (IdRandGlobal and IdRandLic). Global identifiers are shared across customers, licensed ones are scoped to the licence key.

The server-side step, which you copy into your own server

The one part of the demo that belongs on your server is the redeem call, because it adds the licence key the browser never sees. The /redeem branch of examples/creator-context-web/server.php is that call, and these are its essential lines. The 51Did, the encrypted result and the challenge arrive from the page, the licence key is the client's, and the answer goes back to the page in the cloud's own shape with one extra field, serverSignature, from the offline signature check.

The branch answers a value that does not read as a 51Did with a 400 naming the status, a NotSupportedException with a 404 and a text body, which the page reports as not supported by this host, an InvalidArgumentException with a 400 and the cloud's errors, and an unreachable cloud with a 502 and { "error": ... }. A production server would also remember the challenge it issued and reject a redemption carrying any other.

Environment variables

Variable Meaning
_51DEGREES_RESOURCE_KEY Required. The resource key of your account, public by nature. The legacy RESOURCE_KEY is read when the aligned name is not set
_51DEGREES_LICENSE_KEY Optional. A licence key of the same account, server side only. The legacy LICENSE_KEY is read when the aligned name is not set. Only an account that holds licence keys needs one to redeem, so an account holding none runs without it
FOD_CLOUD_API_URL Optional. The cloud API base including the /api/v4/ segment, defaulting to https://cloud.51degrees.com/api/v4/. This is the same variable the cloud request engine honours. A host other than cloud.51degrees.com would be used to (a) use an on premise web server, or (b) use a privately hosted version of the 51Degrees cloud for performance reasons. This is the private hosting option of the cloud service. Both run the same service, so the demo works unchanged against either

How to run

Run composer install at the repository root once, then from the examples/creator-context-web folder start the built-in server and open http://localhost:5100/ in a browser. The port is the php -S argument.

To demonstrate across two devices, serve on an address both can reach and open the copied link on the second device.

What a run costs

Every call the demo makes to the cloud is one use against the subscription behind the resource key. Checking a 51Did from the browser makes two, verify-full from the page and redeem from the server, so a browser-based context check is two uses every time. Checking only the signature with verify is one use. The offline signature check in the demo's /redeem fetches the public key list, one more use each time, and under PHP's built-in server that is every redemption because each request starts afresh, whereas an application server keeping one DidClient alive fetches the list once a day.

The copy-and-paste proof

Once the 51Did has fully validated, the page shows a copy-and-paste section with a link carrying the same 51Did, and an explanation of what will happen next. Open that link in a different browser and the same page loads with the same identifier. The signature still verifies and the identifier unpacks, because it is genuine, but the creator context does not validate, because the context binds the identifier to the browser and connection it was created on. That visible failure is the demonstration that matters, a copied or stolen identifier caught at presentation with nothing stored server side. Opening the link in the same browser is not the demonstration, since the same browser presents the same context and may still verify.

Stylesheet

The examples-main.min.css vendored beside page.html is the 51Degrees examples design system build and is refreshed by common-ci's update-example-assets step.


All versions of fiftyone.pipeline.did with dependencies

PHP Build Version
Package Version
Requires php Version >=8.1
ext-json Version *
ext-openssl Version *
ext-mbstring Version *
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package 51degrees/fiftyone.pipeline.did contains the following files

Loading the files please wait ...