📜 ⬆️ ⬇️

MNP and Appearance API 1.0 - Voluntary Eating Cacti

Foreword


During many years of experience in IT, many of you have had thoughts or even experience in creating your own API, which would contain all the most useful and valuable things, and at some point the puzzle develops and begins the stage of growing and eating your own cactus.
After a year of running in, before further modification and development, I want to share the resulting functionality of the mobile operator definition service by phone number (RF only), subject the API itself and service to your criticism :)
The article has examples in PHP for running in on its side for those to whom it will be useful in exchange for valuable comments ;-)

Magic Pendel in the face of the abolition of mobile slavery


Back in the times of 2003-2004, as I remember now, a client came to fill out a paper form to replenish the phone’s account. In the operator's column, the user indicated Beeline, and the painfully familiar code 916 clearly told me that this was MTS and the payment would not be blessed, to which the client reassured me: “This number has been transferred to another operator,” he said.
After 10 years, this procedure was already legally approved and, finally, earned, only IT specialists received a slight increase in the load in the form of preparing systems for recognizing operators by number, taking into account this number portability from operator to operator - MNP.

Data appearance


As usual, everything centralized has a single registry. In this case, there were two of them: one - with rarely changing data - they determined that the numbers belonged to operators in large blocks, for example, 916 XXX-XX-XX. The second one is with dynamic numbers, the same transferred numbers - that is, each number was a separate entry.
Omit technical details. There simply was a large layer, on top of which individual numbers “tusked” - first they looked at the operator of the range, then they looked to see if there were any records about the transfer of the number to another operator. The reservoir was updated once a month, individual rooms - every hour (as the numbers are transferred).

Storage and update


Realizing that a more capital approach is needed, it was decided not to do on the knee ... And what is the knee here - full-fledged contracts between legal entities to gain access to the dynamic part of the base, regulated synchronization is no longer sewn to each individual project that used these data is cumbersome. The center was clearly visible - the place of data storage.
Offtopic: a little about the significance of the operator
Imagine a call center. He makes a lot of calls and applies various schemes of operator selection for a call — for calls to MGTS numbers, his own joint is used, and with mobile calls, his own. It became all the sadder and sadder when more and more calls were made through the wrong channels as subscribers migrated — the mistakenly chosen channel was the loss of quite specific rubles that mercilessly ate the employees ’bonus fund.

')

Appearance Center - API


Sketching the first couple of scripts that would give the data is not tricky. But one thing to do for yourself, another - for products that were on the side of the customer. Moreover, the idea of ​​a useful application for Android was in the air, and this already smelled of access to the outside world, possible attacks to “break” the application, and just shkolota, sometimes, out of curiosity and self-affirmation, likes to break something. Therefore, a sudden FRONT-END, AAA, BACK-END and request handlers suddenly appear.
Pro handler ...
I really didn’t want the API client requests to be processed on the fly somewhere on BACK-END — it seemed much more interesting to stack the disk requests and process them with a number of individual servers that can be easily scaled. On and agreed.

Visible requirements:


"Hello, World!" - the most difficult thing on the simplest example


Here is the most interesting. Appeal to the API begins with authorization - getting a token for further interaction with the system. As input data, as many as three identifying values ​​are used - username, telephone, e-mail + password.
Shozred!?
There are many variables in our life:
  • corporate e-mail was not allowed to be taken from the previous place of work
  • the phone went unchecked to a minus during a business trip and the operator took the number
  • password is compromised

it turns out that only the user name is immutable, and that “bye” seems to me. And everything else is very inconsistent ... And to change one props when there are two others is not a problem and it is quite equivalent to authorization in several stages (two-factor).

Only one token per unit of time is issued per account. It can be changed, which will cause a “revocation” of the powers of the scripts and applications that worked under it. This is both good and bad, but the main thing is controlled.
Note about tokens
One of the services is designed to register accounts.
Imagine that a site visitor wants to use some functionality of the service on a third-party site - the site itself will request a new account for it, authorize (= get a token), report a token in the parameters of the web browser sessions for interacting with the service. By means of the browser, the user will be able to make requests himself, but you will have the “steering wheel” of the sessions not only inside your portal, but you will also close the access to the external API by changing the token if you consider that the user should no longer receive access to information.

But before that, we go and pull the public key to interact over unsafe networks without SSL and then prepare the data.
Practical example: `SSL banned`
Who knows, maybe we do not have access to 443 ports - I still remember one case, the paranoia of the Security Service of one large office, which wanted to “listen” and did not allow SSL, even the server: - / There was simply no other way.

$rsa_key = file_get_contents ('http://core.api.netresult.ru/rsa_public.pem'); $client_secret = md5(time()); $data = json_encode( array ('request_type' => 'auth', 'username' =>'habr_demo_20160426', 'email' => 'support@media24-corp.ru', 'phone' => '74995799366', 'password' => md5('megapass'), 'client_secret' => $client_secret) ); $pk = openssl_get_publickey($rsa_key); openssl_public_encrypt($data, $encrypted, $pk); $data = array('body' => chunk_split(base64_encode($encrypted))); 


This is such a nightmare for the sake of a simple request - this heap of manipulations is intended to warn against changing data during transmission over communication channels and for easy validation on FRONT-END.
 function api_build_url ( $scheme='https', //  - http|https $domain='core.api.netresult.ru', //  API,   - core.api.netresult.ru $validation_string_prefix='s', //   ,    (   ) $validation_secret='earth', //    ,   FRONT-END -  FRONT-END`    URI (   URI) $data='', // POST .     $data['body'] $api_version='3', //  API,      $sla=1, //  SLA.  - .  ,  .    .      -   ,     ,   . $uid=0, //     API (AAA). 0  ,    UID $srv_name='AUTHENTICATE', // ,          $client_secret //   ,    .    ,   API    . ) { $url_template = $scheme . '://' . $domain . '/' . $validation_string_prefix . '/__NGINX_SIGN__/__QUERY_STRING__'; $request_string = 'v' . $api_version . '-sla' . $sla . '-uid' . $uid . '-sign__SOMESIGN__-req' . $srv_name; $request_string = str_replace ('__SOMESIGN__', $client_secret, $request_string); $request_string .= '=ts:' . time(); echo "request_string before md5: " . $request_string . PHP_EOL; if (isset($data['body']) === true) { $request_string .= '=postmd5:' . md5($data['body']); } $request_string = str_replace($client_secret, md5($request_string), $request_string); $url = str_replace ('__QUERY_STRING__', $request_string, $url_template); $request_string .= $validation_secret; $query_sign = md5($request_string); $url = str_replace ('__NGINX_SIGN__', $query_sign, $url); return $url; } 


Finally, we got to authorization - we get a token and our UID. This operation can be done once per account, if there is no need to change the secret / token.
 $url = api_build_url ('http', 'core.api.netresult.ru', 's', 'earth', $data, '3', 1, 0, 'AUTHENTICATE', $client_secret); $options = array( 'http' => array( 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'POST', 'content' => http_build_query($data), ), ); $context = stream_context_create($options); $result = file_get_contents($url, false, $context); print_r ($result); /* secret: 50c927316191f8678cec3b5247d1b34f request_string before md5: v3-sla1-uid0-sign50c927316191f8678cec3b5247d1b34f-reqAUTHENTICATE=ts:1461624725 {"response":{"code":"200","data":{"uid":"36","sla":"1"},"msg":"authenticated"}} */ 


Further credentials are not used - only $ client_secret for signing. By the way, I’ll immediately answer the question - yes, you can specify https in the function parameters for interacting with SSL, if the requests are rare and there are no goals that save resources (or if they are spent on the client’s browser side. But it’s clear that we don’t give the client an authorization function , but the client can perform requests from the token on his own over SSL if necessary.).

So, logged in. Now we assign the correct UID and SLA to ourselves, we execute the test request.
 $uid = $result['response']['data']['uid']; $sla = $result['response']['data']['sla']; echo PHP_EOL . PHP_EOL . "TEST SERVICE:" . PHP_EOL; $url = api_build_url ('http', 'core.api.netresult.ru', 's', 'earth', '', '3', $sla, $uid, 'TEST', $client_secret); $options = array( 'http' => array( 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'GET' ), ); $context = stream_context_create($options); $result = file_get_contents($url, false, $context); print_r ($result); /* TEST SERVICE: request_string before md5: v3-sla1-uid36-sign50c927316191f8678cec3b5247d1b34f-reqTEST=ts:1461624725 {"response":{"code":"200","data":{"id":"21967","sla":"1","uid":"36","sign":"9cf6cba26113c52abea3af928f6232b6","srv":"test","req_head":"TEST=ts:1461624725","req_body":"","api_version":"3"},"msg":"ok"}} */ 

Yeah, ok!

We benefit


By analogy, we turn to the more useful service LOOKUP_BY_NUMBER, for which additional parameters are required:
number - ( "+")
source - (all - ),

and additional fields can be set if necessary (in a mobile application, for example, to identify the devices themselves and correctly form the answer)
v -
did -
loc -
format -

 echo PHP_EOL . PHP_EOL . "LOOKUP_BY_NUMBER SERVICE:" . PHP_EOL; $url = api_build_url ('http', 'core.api.netresult.ru', 's', 'earth', '', '3', $sla, $uid, 'LOOKUP_BY_NUMBER'.'=number:+79671286464=v:6=loc:ru_RU=did:000000000000000000000000=format:no=source:all', $client_secret); $options = array( 'http' => array( 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'GET' ), ); $context = stream_context_create($options); $result = file_get_contents($url, false, $context); print_r ($result); /* LOOKUP_BY_NUMBER SERVICE: request_string before md5: v3-sla1-uid36-sign0e70a22605e5a178b572f0bfe426d210-reqLOOKUP_BY_NUMBER=number:+79671286464=v:6=loc:ru_RU=did:000000000000000000000000=format:no=source:all=ts:1461625693 {"response":{"code":200,"data":{"input":{"number":{"original":"+79671286464","parsed":"79671286464"}},"route":{"default":{"ServiceProvider_id":"3425721","ServiceProvider_name":" \"-\"","ServiceProvider_region":".    "},"mnp":{"ServiceProvider_id":2,"ServiceProvider_name":"\" \" ","ServiceProvider_region":".    "}},"active_route":{"type":"mnp"}},"msg":""}} */ 


Return values

The result of the query will be the routing table in order of priority. The first record of the source is active. The rest are just in the database, but most likely outdated. The output depends on the appropriate “format” setting.
IMPORTANT: Service Provider ID return internal for each data source! It is safe to consolidate by ID within one source.

Example output:
 { "response": { "code": 200, "data": { "input": { "number": { "original": "+79781234567", "parsed": "79781234567" } }, "route": { "default": { "ServiceProvider_id": "274959", "ServiceProvider_name": " ", "ServiceProvider_region": " " } }, "active_route": "default" }, "msg": "" } } 


Methods of sending data

You can use the simplest method GET, example:
http://core.api.netresult.ru/s/f62bb062e30b01e7a689c8cdfdf10577/v3-sla1-uid0-signbb52d15783f7ed30df5d0e7a618cc048-reqLOOKUP_BY_NUMBER=number:+79781234567=v:6=loc:ru_RU=did:000000000000000000000000=format:no=source:all

and you can send the values ​​of variables and in POST-data with encryption by the example of authorization above.

An example of an API-based mobile application for Android with minimal functionality of the application itself.
image
.

Comments and reviews


I am very grateful for any criticism regarding everything and everything that you consider necessary to comment!

Source: https://habr.com/ru/post/282443/


All Articles