├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── config
└── bitrix24.php
├── database
└── migrations
│ ├── 2024_11_15_151001_create_b24_apps_table.php
│ └── 2024_11_15_151002_create_b24_users_table.php
├── phpunit.xml
├── resources
└── views
│ ├── index.blade.php
│ ├── install-fail.blade.php
│ └── install.blade.php
├── routes
├── b24app.php
├── b24appFrontRequest.php
├── b24appUser.php
└── web.php
└── src
├── Adapters
└── EventDispatcherAdapter.php
├── Application
└── Local
│ └── Infrastructure
│ └── Database
│ ├── AppAuthDatabaseStorage.php
│ └── UserAuthDatabaseStorage.php
├── Bitrix24ApiClient.php
├── Bitrix24App.php
├── Bitrix24ServiceProvider.php
├── Bitrix24User.php
├── Http
├── Controllers
│ └── Bitrix24
│ │ ├── AppController.stub
│ │ ├── Events
│ │ ├── OnApplicationInstallController.stub
│ │ └── OnApplicationUninstallController.stub
│ │ └── InstallController.stub
└── Middleware
│ ├── B24AppMiddleware.php
│ ├── B24AppUserMiddleware.php
│ └── B24AuthUserMiddleware.php
├── Listeners
└── PortalDomainUrlChangedListener.php
└── Models
├── B24App.php
└── B24User.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 X3Group
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Пакет Laravel для удобной работы с REST API Битрикс24 и написания приложений.
2 |
3 | Включает в себя:
4 | - Миграции для сбора статистики запросов и сохранения авторизации(токенов) пользователей
5 | - Роуты в зависимости от типа приложения и запросов к нему
6 | - Шаблоны для установки и работы приложения
7 | - Проверку статуса порталов на которые было установлено приложение
8 | - Автоматическое обновление токенов пользователей
9 |
10 | Установка
11 |
12 | ```injectablephp
13 | composer require x3group-dev/bitrix24-api-laravel
14 | ```
15 |
16 | Выполнить публикацию (скопируются routes, blade, базовые контроллеры)
17 | ```injectablephp
18 | php artisan vendor:publish --provider="X3Group\Bitrix24\Bitrix24ServiceProvider"
19 | ```
20 |
21 | Выполнить миграции
22 | ```injectablephp
23 | php artisan migrate
24 | ```
25 |
26 | В адреса приложений вписываем
27 |
28 | Приложение:
29 | ```injectablephp
30 | https://host/app
31 | ```
32 | Установка приложения:
33 | ```injectablephp
34 | https://host/install
35 | ```
36 |
37 | в файл .env добавляем и заполняем своими данными
38 | ```injectablephp
39 | BITRIX24_PHP_SDK_APPLICATION_CLIENT_ID=
40 | BITRIX24_PHP_SDK_APPLICATION_CLIENT_SECRET=
41 | # вместо crm,user_brief укажите скоупы приложения
42 | BITRIX24_PHP_SDK_APPLICATION_SCOPE="crm,user_brief"
43 | BITRIX24_LOG_MAX_FILES=3
44 | ```
45 |
46 | В сборку фронта добавить проброс авторизации в заголовках, чтобы работали роуты b24appFrontRequest
47 |
48 | ```injectablephp
49 | BX24.ready(async function () {
50 | await BX24.init(async function () {
51 | window.axios.defaults.headers.common['X-b24api-access-token'] = BX24.getAuth().access_token;
52 | window.axios.defaults.headers.common['X-b24api-refresh-token'] = BX24.getAuth().refresh_token;
53 | window.axios.defaults.headers.common['X-b24api-domain'] = BX24.getAuth().domain;
54 | window.axios.defaults.headers.common['X-b24api-member-id'] = BX24.getAuth().member_id;
55 | window.axios.defaults.headers.common['X-b24api-expires-in'] = BX24.getAuth().expires_in;
56 | });
57 | });
58 | ```
59 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "x3group-dev/bitrix24-api-laravel",
3 | "description": "b24 rest api",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "x3Group",
9 | "email": "dev@x3group.ru"
10 | },
11 | {
12 | "name": "MrDeff",
13 | "email": "e.pedan@gmail.com",
14 | "role": "Developer"
15 | },
16 | {
17 | "name": "Maxim Yugov",
18 | "email": "maxim.s.yugov@gmail.com",
19 | "role": "Developer"
20 | }
21 | ],
22 | "keywords": ["Laravel", "Bitrix", "Bitrix24"],
23 | "require": {
24 | "illuminate/support": "^11.0|^12.0",
25 | "bitrix24/b24phpsdk": "^1.1"
26 | },
27 | "require-dev": {
28 | "phpunit/phpunit": "~9.0",
29 | "orchestra/testbench": "~7"
30 | },
31 | "autoload": {
32 | "psr-4": {
33 | "X3Group\\Bitrix24\\": "src/"
34 | }
35 | },
36 | "autoload-dev": {
37 | "psr-4": {
38 | "X3Group\\Bitrix24\\Tests\\": "tests"
39 | }
40 | },
41 | "extra": {
42 | "laravel": {
43 | "providers": [
44 | "X3Group\\Bitrix24\\Bitrix24ServiceProvider"
45 | ],
46 | "aliases": {
47 | "Bitrix24ApiLaravel": "Bitrix24"
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/config/bitrix24.php:
--------------------------------------------------------------------------------
1 | env('BITRIX24_PHP_SDK_APPLICATION_CLIENT_ID'),
5 | 'client_secret' => env('BITRIX24_PHP_SDK_APPLICATION_CLIENT_SECRET'),
6 | 'scope' => env('BITRIX24_PHP_SDK_APPLICATION_SCOPE'),
7 | 'log_max_files' => env('BITRIX24_LOG_MAX_FILES', 3),
8 | ];
9 |
--------------------------------------------------------------------------------
/database/migrations/2024_11_15_151001_create_b24_apps_table.php:
--------------------------------------------------------------------------------
1 | id();
13 |
14 | $table->string('member_id')->unique();
15 | $table->string('domain');
16 | $table->string('application_token')->nullable();
17 | $table->string('access_token');
18 | $table->string('refresh_token');
19 | $table->integer('expires_in');
20 | $table->integer('expires');
21 |
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | public function down(): void
27 | {
28 | Schema::dropIfExists('b24_apps');
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/database/migrations/2024_11_15_151002_create_b24_users_table.php:
--------------------------------------------------------------------------------
1 | id();
13 |
14 | $table->string('member_id');
15 |
16 | $table->foreign('member_id')
17 | ->references('member_id')
18 | ->on('b24_apps')
19 | ->cascadeOnDelete();
20 |
21 | $table->string('domain');
22 | $table->integer('user_id');
23 | $table->boolean('is_admin');
24 | $table->string('access_token');
25 | $table->string('refresh_token');
26 | $table->integer('expires_in');
27 | $table->integer('expires');
28 |
29 | $table->timestamps();
30 |
31 | $table->unique(['member_id', 'user_id']);
32 | });
33 | }
34 |
35 | public function down(): void
36 | {
37 | Schema::dropIfExists('b24_users');
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./tests/
15 |
16 |
17 |
18 |
19 | src/
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/resources/views/index.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Приложение
7 |
8 |
9 |
10 |
11 | Приложение работает!
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/resources/views/install-fail.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Установка
7 |
8 |
9 | Ошибка при установке приложения. Повторите попытку позже, либо обратитесь в тех поддержку
10 |
11 |
12 |
--------------------------------------------------------------------------------
/resources/views/install.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Установка
7 |
12 |
13 |
14 | Установка успешно завершена
15 |
16 |
17 |
--------------------------------------------------------------------------------
/routes/b24app.php:
--------------------------------------------------------------------------------
1 | group(function () {
15 | Route::post('/onApplicationInstall', [OnApplicationInstallController::class, 'handle']);
16 | Route::post('/onApplicationUninstall', [DemoOnApplicationUninstallController::class, 'handle']);
17 | });
18 |
--------------------------------------------------------------------------------
/routes/b24appFrontRequest.php:
--------------------------------------------------------------------------------
1 | dispatcher = resolve('events');
17 | }
18 |
19 | public function dispatch(object $event, ?string $eventName = null): object
20 | {
21 | $r = $this->dispatcher->dispatch($event);
22 |
23 | return $event;
24 | }
25 |
26 | public function listen(array|Closure|QueuedClosure|string $events, array|Closure|QueuedClosure|null|string $listener): void
27 | {
28 | $this->dispatcher->listen($events, $listener);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Application/Local/Infrastructure/Database/AppAuthDatabaseStorage.php:
--------------------------------------------------------------------------------
1 | where('member_id', $this->memberId)
25 | ->first();
26 |
27 | if (!$b24app) {
28 | throw new \Exception('Application is not installed');
29 | }
30 |
31 | return LocalAppAuth::initFromArray([
32 | 'auth_token' => [
33 | 'access_token' => $b24app->access_token,
34 | 'refresh_token' => $b24app->refresh_token,
35 | 'expires' => $b24app->expires,
36 | ],
37 | 'domain_url' => "https://{$b24app->domain}",
38 | 'application_token' => $b24app->application_token,
39 | ]);
40 | }
41 |
42 | /**
43 | * @inheritDoc
44 | */
45 | public function getApplicationToken(): ?string
46 | {
47 | $b24app = B24App::query()
48 | ->where('member_id', $this->memberId)
49 | ->first();
50 |
51 | return $b24app?->application_token ?? null;
52 | }
53 |
54 | /**
55 | * @inheritDoc
56 | */
57 | public function saveRenewedToken(RenewedAuthToken $renewedAuthToken): void
58 | {
59 | $b24app = B24App::query()
60 | ->where('member_id', $renewedAuthToken->memberId)
61 | ->first();
62 |
63 | if (!$b24app) {
64 | throw new \Exception('App token not found');
65 | }
66 |
67 | $b24app->access_token = $renewedAuthToken->authToken->accessToken;
68 | $b24app->refresh_token = $renewedAuthToken->authToken->refreshToken;
69 | $b24app->expires_in = $renewedAuthToken->authToken->expiresIn ?? 3600;
70 | $b24app->expires = $renewedAuthToken->authToken->expires;
71 |
72 | $b24app->save();
73 | }
74 |
75 | /**
76 | * @inheritDoc
77 | */
78 | public function save(LocalAppAuth $localAppAuth): void
79 | {
80 | $expiresIn = $localAppAuth->getAuthToken()->expiresIn;
81 |
82 | if ($expiresIn === null) {
83 | $expiresIn = now();
84 | $expiresIn->addSeconds($localAppAuth->getAuthToken()->expires);
85 | $expiresIn = $expiresIn->timestamp;
86 | }
87 |
88 | $b24api = B24App::query()
89 | ->where('member_id', $this->memberId)
90 | ->first();
91 |
92 | if ($b24api === null) {
93 | B24App::query()
94 | ->create([
95 | 'access_token' => $localAppAuth->getAuthToken()->accessToken,
96 | 'refresh_token' => $localAppAuth->getAuthToken()->refreshToken,
97 | 'expires' => $expiresIn,
98 | 'expires_in' => $localAppAuth->getAuthToken()->expires,
99 | 'application_token' => $localAppAuth->getApplicationToken(),
100 | 'domain' => $localAppAuth->getDomainUrl(),
101 | 'member_id' => $this->memberId,
102 | ]);
103 | } else {
104 | $b24api->access_token = $localAppAuth->getAuthToken()->accessToken;
105 | $b24api->refresh_token = $localAppAuth->getAuthToken()->refreshToken;
106 | $b24api->expires = $expiresIn;
107 | $b24api->expires_in = $localAppAuth->getAuthToken()->expires;
108 | $b24api->application_token = $localAppAuth->getApplicationToken();
109 | $b24api->domain = $localAppAuth->getDomainUrl();
110 |
111 | $b24api->save();
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Application/Local/Infrastructure/Database/UserAuthDatabaseStorage.php:
--------------------------------------------------------------------------------
1 | with(B24App::class)
27 | ->where('member_id', $this->memberId)
28 | ->where('user_id', $this->userId)
29 | ->first();
30 |
31 | if (!$b24user) {
32 | throw new \Exception('User token not found');
33 | }
34 |
35 | return LocalAppAuth::initFromArray([
36 | 'auth_token' => [
37 | 'access_token' => $b24user->access_token,
38 | 'refresh_token' => $b24user->refresh_token,
39 | 'expires' => $b24user->expires,
40 | ],
41 | 'domain_url' => "https://{$b24user->domain}",
42 | 'application_token' => $b24user->b24app()->application_token,
43 | ]);
44 | }
45 |
46 | /**
47 | * @inheritDoc
48 | */
49 | public function getApplicationToken(): ?string
50 | {
51 | $b24user = B24User::query()
52 | ->with(B24App::class)
53 | ->where('member_id', $this->memberId)
54 | ->where('user_id', $this->userId)
55 | ->first();
56 |
57 | return $b24user?->b24app()->application_token ?? null;
58 | }
59 |
60 | /**
61 | * @inheritDoc
62 | */
63 | public function saveRenewedToken(RenewedAuthToken $renewedAuthToken): void
64 | {
65 | $b24user = B24User::query()
66 | ->where('member_id', $renewedAuthToken->memberId)
67 | ->where('user_id', $this->userId)
68 | ->first();
69 |
70 | if (!$b24user) {
71 | throw new \Exception('User token not found');
72 | }
73 |
74 | $b24user->access_token = $renewedAuthToken->authToken->accessToken;
75 | $b24user->refresh_token = $renewedAuthToken->authToken->refreshToken;
76 | $b24user->expires_in = $renewedAuthToken->authToken->expiresIn ?? 3600;
77 | $b24user->expires = $renewedAuthToken->authToken->expires;
78 |
79 | $b24user->save();
80 | }
81 |
82 | /**
83 | * @inheritDoc
84 | */
85 | public function save(LocalAppAuth $localAppAuth): void
86 | {
87 | $expiresIn = $localAppAuth->getAuthToken()->expiresIn;
88 |
89 | if ($expiresIn === null) {
90 | $expiresIn = now();
91 | $expiresIn->addSeconds($localAppAuth->getAuthToken()->expires);
92 | $expiresIn = $expiresIn->timestamp;
93 | }
94 |
95 | $b24api = B24User::query()
96 | ->where('member_id', $this->memberId)
97 | ->where('user_id', $this->userId)
98 | ->first();
99 |
100 | if ($b24api === null) {
101 | B24User::query()
102 | ->create([
103 | 'access_token' => $localAppAuth->getAuthToken()->accessToken,
104 | 'refresh_token' => $localAppAuth->getAuthToken()->refreshToken,
105 | 'expires' => $expiresIn,
106 | 'expires_in' => $localAppAuth->getAuthToken()->expires,
107 | 'domain' => $localAppAuth->getDomainUrl(),
108 | 'member_id' => $this->memberId,
109 | ]);
110 | } else {
111 | $b24api->access_token = $localAppAuth->getAuthToken()->accessToken;
112 | $b24api->refresh_token = $localAppAuth->getAuthToken()->refreshToken;
113 | $b24api->expires = $expiresIn;
114 | $b24api->expires_in = $localAppAuth->getAuthToken()->expires;
115 | $b24api->domain = $localAppAuth->getDomainUrl();
116 |
117 | $b24api->save();
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Bitrix24ApiClient.php:
--------------------------------------------------------------------------------
1 | memberId = $memberId;
25 |
26 | $applicationProfile = new ApplicationProfile(
27 | clientId: config('bitrix24.client_id'),
28 | clientSecret: config('bitrix24.client_secret'),
29 | scope: Scope::initFromString(config('bitrix24.scope'))
30 | );
31 |
32 | $b24api = B24App::query()
33 | ->where('member_id', $memberId)
34 | ->first();
35 |
36 | $authToken = new AuthToken(
37 | accessToken: $b24api->access_token,
38 | refreshToken: $b24api->refresh_token,
39 | expires: $b24api->expires,
40 | expiresIn: $b24api->expires_in,
41 | );
42 |
43 | /** @var EventDispatcher $eventDispatcher */
44 | $eventDispatcher = resolve('appEvents');
45 |
46 | $app = new ServiceBuilderFactory(
47 | eventDispatcher: $eventDispatcher,
48 | log: resolve('b24log', [
49 | 'memberId' => $memberId
50 | ]),
51 | );
52 |
53 | $this->api = $app->init(
54 | applicationProfile: $applicationProfile,
55 | authToken: $authToken,
56 | bitrix24DomainUrl: "https://{$b24api->domain}",
57 | );
58 | }
59 |
60 | public function getMemberId(): string
61 | {
62 | return $this->memberId;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Bitrix24ServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadTranslationsFrom(__DIR__.'/../resources/lang', 'x3group');
53 | // $this->loadViewsFrom(__DIR__.'/../resources/views', 'x3group');
54 | // $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
55 | // $this->loadRoutesFrom(__DIR__.'/routes.php');
56 |
57 | // Publishing is only necessary when using the CLI.
58 | if ($this->app->runningInConsole()) {
59 | $this->bootForConsole();
60 | }
61 |
62 | $application = $kernel->getApplication();
63 | $router = $application->make(Router::class);
64 |
65 | /**
66 | * Защита для приложений типа: использует только API
67 | */
68 | $router->middlewareGroup('b24app', [
69 | EncryptCookies::class,
70 | AddQueuedCookiesToResponse::class,
71 | StartSession::class,
72 | ShareErrorsFromSession::class,
73 | SubstituteBindings::class,
74 | B24AppMiddleware::class,
75 | ]);
76 |
77 | /**
78 | * Первичный вход на приложение, сохранение авторизации пользователя (laravel),
79 | * авторизация его в рамках приложения и laravel
80 | *
81 | * При включенных ThirdParty cookie авторизация б24 берется из сессии
82 | * Хождение в рамках приложения с отключенной проверкой CsrfToken
83 | *
84 | * Для приложений с интерфейсом
85 | */
86 | $router->middlewareGroup('b24appUser', [
87 | EncryptCookies::class,
88 | AddQueuedCookiesToResponse::class,
89 | StartSession::class,
90 | ShareErrorsFromSession::class,
91 | SubstituteBindings::class,
92 | B24AppUserMiddleware::class,
93 | ]);
94 |
95 | /**
96 | * Запросы из фронта приложения с передачей авторизации через header X-b24api-access-token X-b24api-domain X-b24api-member-id
97 | * авторизует пользователя и делает запрос от него
98 | */
99 | $router->middlewareGroup('b24appFrontRequest', [
100 | EncryptCookies::class,
101 | AddQueuedCookiesToResponse::class,
102 | StartSession::class,
103 | ShareErrorsFromSession::class,
104 | SubstituteBindings::class,
105 | B24AuthUserMiddleware::class,
106 | ]);
107 |
108 | $router->group(['middleware' => 'b24app'], function () {
109 | if (file_exists(base_path('routes/b24app.php')))
110 | $this->loadRoutesFrom(base_path('routes/b24app.php'));
111 | });
112 |
113 | $router->group(['middleware' => 'b24appUser'], function () {
114 | if (file_exists(base_path('routes/b24appUser.php')))
115 | $this->loadRoutesFrom(base_path('routes/b24appUser.php'));
116 | });
117 |
118 | $router->group(['middleware' => 'b24appFrontRequest'], function () {
119 | if (file_exists(base_path('routes/b24appFrontRequest.php')))
120 | $this->loadRoutesFrom(base_path('routes/b24appFrontRequest.php'));
121 | });
122 |
123 | $this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
124 |
125 | $this->publishes([
126 | __DIR__ . '/../routes/b24app.php' => base_path('routes/b24app.php'),
127 | __DIR__ . '/../routes/b24appUser.php' => base_path('routes/b24appUser.php'),
128 | __DIR__ . '/../routes/b24appFrontRequest.php' => base_path('routes/b24appFrontRequest.php'),
129 |
130 | __DIR__ . '/../resources/views' => resource_path('views/b24api'),
131 |
132 | __DIR__ . '/Http/Controllers/Bitrix24/AppController.stub' => base_path('app/Http/Controllers/Bitrix24/AppController.php'),
133 | __DIR__ . '/Http/Controllers/Bitrix24/InstallController.stub' => base_path('app/Http/Controllers/Bitrix24/InstallController.php'),
134 |
135 | __DIR__ . '/Http/Controllers/Bitrix24/Events/OnApplicationInstallController.stub' => base_path('app/Http/Controllers/Bitrix24/Events/OnApplicationInstallController.php'),
136 | __DIR__ . '/Http/Controllers/Bitrix24/Events/OnApplicationUninstallController.stub' => base_path('app/Http/Controllers/Bitrix24/Events/OnApplicationUninstallController.php'),
137 | ], 'bitrix24-routes');
138 |
139 | $this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
140 | }
141 |
142 | /**
143 | * Register any package services.
144 | *
145 | * @return void
146 | */
147 | public function register(): void
148 | {
149 | $this->mergeConfigFrom(__DIR__.'/../config/bitrix24.php', 'bitrix24');
150 |
151 | $this->app->bind('appEvents', function () {
152 | $eventDispatcher = new EventDispatcherAdapter();
153 | $eventDispatcher->listen(AuthTokenRenewedEvent::class, function (AuthTokenRenewedEvent $event) {
154 | /** @var AppAuthDatabaseStorage $appAuthStorage */
155 | $appAuthStorage = resolve(AppAuthDatabaseStorage::class, [
156 | 'memberId' => $event->getRenewedToken()->memberId,
157 | ]);
158 | $appAuthStorage->saveRenewedToken($event->getRenewedToken());
159 | });
160 | //$eventDispatcher->listen(PortalDomainUrlChangedEvent::class, function (PortalDomainUrlChangedEvent $event) {
161 | // \logger('change url event');
162 | // $listener = new PortalDomainUrlChangedListener();
163 | // $listener->handle($event);
164 | //});
165 |
166 | return $eventDispatcher;
167 | });
168 |
169 | $this->app->bind('userEvents', function (Application $app, array $parameters) {
170 | $eventDispatcher = new EventDispatcherAdapter();
171 |
172 | if (isset($parameters['memberId']) && isset($parameters['userId'])) {
173 | $eventDispatcher->listen(
174 | events: AuthTokenRenewedEvent::class,
175 | listener: function (AuthTokenRenewedEvent $event) use ($parameters) {
176 |
177 | resolve(UserAuthDatabaseStorage::class, [
178 | 'memberId' => $event->getRenewedToken()->memberId,
179 | 'userId' => $parameters['userId'],
180 | ])->saveRenewedToken($event->getRenewedToken());
181 | });
182 | }
183 |
184 | return $eventDispatcher;
185 | });
186 |
187 | $this->app->bind('b24log', function (Application $app, array $parameters) {
188 | $memberId = $parameters['memberId'];
189 | $domain = $parameters['domain'] ?? 'unknown';
190 |
191 | /** @var B24App $b24app */
192 | $b24app = B24App::query()
193 | ->where('member_id', $memberId)
194 | ->first();
195 |
196 | if ($b24app) {
197 | $domain = $b24app->domain;
198 | }
199 |
200 | $logger = new Logger('b24log');
201 | $logger->pushHandler(new RotatingFileHandler(
202 | filename: storage_path('logs/b24api/' . $domain . '-' . $memberId . '/b24api.log'),
203 | maxFiles: config('bitrix24.log_max_files'),
204 | ));
205 |
206 | return $logger;
207 | });
208 |
209 | $this->app->bind('bitrix24user', function (Application $app, array $parameters) {
210 | return new Bitrix24User($parameters['memberId'], $parameters['userId']);
211 | });
212 |
213 | $this->app->bind('bitrix24app', function (Application $app, array $parameters) {
214 | return new Bitrix24App($parameters['memberId']);
215 | });
216 |
217 | $this->app->bind(AppAuthDatabaseStorage::class, function (Application $app, array $parameters) {
218 | return new AppAuthDatabaseStorage($parameters['memberId']);
219 | });
220 |
221 | $this->app->bind(ApplicationProfile::class, function () {
222 | return new ApplicationProfile(
223 | clientId: config('bitrix24.client_id'),
224 | clientSecret: config('bitrix24.client_secret'),
225 | scope: Scope::initFromString(config('bitrix24.scope'))
226 | );
227 | });
228 |
229 | $this->app->bind(Bitrix24ApiClient::class, function () {
230 | //
231 | $applicationProfile = new ApplicationProfile(
232 | clientId: config('bitrix24.client_id'),
233 | clientSecret: config('bitrix24.client_secret'),
234 | scope: Scope::initFromString(config('bitrix24.scope'))
235 | );
236 |
237 | $memberId = null;
238 |
239 | $request = Request::createFromGlobals();
240 |
241 | if ($request->has('auth') && !empty($request->input('auth')['member_id'])) {
242 | $memberId = $request->input('auth')['member_id'];
243 | } elseif ($request->has('member_id') && !empty($request->input('member_id'))) {
244 | $memberId = $request->input('member_id');
245 | }
246 |
247 | if (is_null($memberId)) {
248 | throw new \Exception('Request has no member_id');
249 | }
250 |
251 | $b24api = B24App::query()
252 | ->where('member_id', $memberId)
253 | ->first();
254 |
255 | $authToken = new AuthToken(
256 | accessToken: $b24api->access_token,
257 | refreshToken: $b24api->refresh_token,
258 | expires: $b24api->expires,
259 | expiresIn: $b24api->expires_in,
260 | );
261 |
262 | $app = new ServiceBuilderFactory(
263 | eventDispatcher: resolve('appEvents'),
264 | log: resolve('b24log', [
265 | 'memberId' => $memberId
266 | ]),
267 | );
268 |
269 | $appClient = $app->init(
270 | applicationProfile: $applicationProfile,
271 | authToken: $authToken,
272 | bitrix24DomainUrl: "https://{$b24api->domain}",
273 | );
274 |
275 | // User
276 | $userClient = ServiceBuilderFactory::createServiceBuilderFromPlacementRequest(
277 | placementRequest: Request::createFromGlobals(),
278 | applicationProfile: $applicationProfile,
279 | eventDispatcher: new EventDispatcherAdapter(),
280 | );
281 |
282 | return new Bitrix24ApiClient(
283 | app: $appClient,
284 | user: $userClient,
285 | );
286 | });
287 |
288 | $this->app->bind(ApiClient::class, function (Application $app, array $parameters) {
289 | return new ApiClient(
290 | credentials: new Credentials(
291 | webhookUrl: null,
292 | authToken: new AuthToken(
293 | accessToken: $parameters['accessToken'],
294 | refreshToken: $parameters['refreshToken'],
295 | expires: $parameters['expires'],
296 | expiresIn: $parameters['expiresIn'],
297 | ),
298 | applicationProfile: new ApplicationProfile(
299 | clientId: config('bitrix24.client_id'),
300 | clientSecret: config('bitrix24.client_secret'),
301 | scope: Scope::initFromString(config('bitrix24.scope'))
302 | ),
303 | domainUrl: "https://{$parameters['domain']}",
304 | ),
305 | client: HttpClient::create(),
306 | requestIdGenerator: new DefaultRequestIdGenerator(),
307 | apiLevelErrorHandler: new ApiLevelErrorHandler(resolve('b24log', [
308 | 'memberId' => $parameters['memberId']
309 | ])),
310 | logger: resolve('b24log', [
311 | 'memberId' => $parameters['memberId']
312 | ]),
313 | );
314 | });
315 |
316 | Event::listen(PortalDomainUrlChangedEvent::class, PortalDomainUrlChangedListener::class);
317 | }
318 |
319 | /**
320 | * Get the services provided by the provider.
321 | *
322 | * @return array
323 | */
324 | public function provides(): array
325 | {
326 | return ['bitrix24'];
327 | }
328 |
329 | /**
330 | * Console-specific booting.
331 | *
332 | * @return void
333 | */
334 | protected function bootForConsole(): void
335 | {
336 | // Publishing the configuration file.
337 | $this->publishes([
338 | __DIR__.'/../config/bitrix24.php' => config_path('bitrix24.php'),
339 | ], 'bitrix24.config');
340 |
341 | // Publishing the views.
342 | /*$this->publishes([
343 | __DIR__.'/../resources/views' => base_path('resources/views/vendor/x3group'),
344 | ], 'bitrix24.views');*/
345 |
346 | // Publishing assets.
347 | /*$this->publishes([
348 | __DIR__.'/../resources/assets' => public_path('vendor/x3group'),
349 | ], 'bitrix24.assets');*/
350 |
351 | // Publishing the translation files.
352 | /*$this->publishes([
353 | __DIR__.'/../resources/lang' => resource_path('lang/vendor/x3group'),
354 | ], 'bitrix24.lang');*/
355 |
356 | // Registering package commands.
357 | // $this->commands([]);
358 | }
359 | }
360 |
--------------------------------------------------------------------------------
/src/Bitrix24User.php:
--------------------------------------------------------------------------------
1 | memberId = $memberId;
26 | $this->userId = $userId;
27 |
28 | $factory = new ServiceBuilderFactory(
29 | eventDispatcher: resolve('userEvents', [
30 | 'memberId' => $memberId,
31 | 'userId' => $userId,
32 | ]),
33 | log: resolve('b24log', [
34 | 'memberId' => $memberId
35 | ]),
36 | );
37 |
38 | $applicationProfile = new ApplicationProfile(
39 | clientId: config('bitrix24.client_id'),
40 | clientSecret: config('bitrix24.client_secret'),
41 | scope: Scope::initFromString(config('bitrix24.scope'))
42 | );
43 |
44 | /** @var B24User $b24user */
45 | $b24user = B24User::query()
46 | ->where('member_id', $memberId)
47 | ->where('user_id', $userId)
48 | ->first();
49 |
50 | if (empty($b24user)) {
51 | throw new \Exception('User not found');
52 | }
53 |
54 | $authToken = new AuthToken(
55 | accessToken: $b24user->access_token,
56 | refreshToken: $b24user->refresh_token,
57 | expires: $b24user->expires,
58 | expiresIn: $b24user->expires_in,
59 | );
60 |
61 | $this->api = $factory->init(
62 | applicationProfile: $applicationProfile,
63 | authToken: $authToken,
64 | bitrix24DomainUrl: "https://{$b24user->domain}",
65 | );
66 | }
67 |
68 | public function getMemberId(): string
69 | {
70 | return $this->memberId;
71 | }
72 |
73 | public function getUserId(): int
74 | {
75 | return $this->userId;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Bitrix24/AppController.stub:
--------------------------------------------------------------------------------
1 | input('auth')['member_id'];
14 | $applicationToken = $request->input('auth')['application_token'];
15 |
16 | $b24app = B24App::query()
17 | ->where('member_id', $memberId)
18 | ->whereNull('application_token')
19 | ->first();
20 |
21 | if ($b24app) {
22 | $b24app->application_token = $applicationToken;
23 | $b24app->save();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Bitrix24/Events/OnApplicationUninstallController.stub:
--------------------------------------------------------------------------------
1 | input('auth')['member_id'];
20 |
21 | $b24app = B24App::query()
22 | ->where('member_id', $memberId)
23 | ->first();
24 |
25 | if ($b24app === null) {
26 | return;
27 | }
28 |
29 | try {
30 | RemoteEventsFabric::init(resolve('b24log', [
31 | 'memberId' => $memberId
32 | ]))
33 | ->createEvent(
34 | request: $request,
35 | applicationToken: $b24app->application_token,
36 | );
37 |
38 | $b24app->delete();
39 | } catch (WrongSecuritySignatureException $e) {
40 | return;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Bitrix24/InstallController.stub:
--------------------------------------------------------------------------------
1 | listen(AuthTokenRenewedEvent::class, function (AuthTokenRenewedEvent $authTokenRenewedEvent): void {
28 | /** @var AppAuthDatabaseStorage $appAuthStorage */
29 | $appAuthStorage = resolve(AppAuthDatabaseStorage::class, [
30 | 'memberId' => $authTokenRenewedEvent->getRenewedToken()->memberId,
31 | ]);
32 | $appAuthStorage->saveRenewedToken($authTokenRenewedEvent->getRenewedToken());
33 | });
34 |
35 | $b24 = ServiceBuilderFactory::createServiceBuilderFromPlacementRequest(
36 | placementRequest: $request,
37 | applicationProfile: new ApplicationProfile(
38 | clientId: config('bitrix24.client_id'),
39 | clientSecret: config('bitrix24.client_secret'),
40 | scope: Scope::initFromString(config('bitrix24.scope')),
41 | ),
42 | eventDispatcher: $eventDispatcher,
43 | logger: resolve('b24log', [
44 | 'memberId' => $request->input('member_id'),
45 | 'domain' => $request->input('DOMAIN')
46 | ]),
47 | );
48 |
49 | $currentB24UserId = $b24->getMainScope()
50 | ->main()
51 | ->getCurrentUserProfile()
52 | ->getUserProfile()
53 | ->ID;
54 |
55 | $authToken = new AuthToken(
56 | accessToken: $request->input('AUTH_ID'),
57 | refreshToken: $request->input('REFRESH_ID'),
58 | expires: (int)$request->input('AUTH_EXPIRES'),
59 | );
60 |
61 | $localAppAuth = new LocalAppAuth(
62 | authToken: $authToken,
63 | domainUrl: $request->input('DOMAIN'),
64 | applicationToken: null,
65 | );
66 |
67 | $memberId = $request->input('member_id');
68 |
69 | $storage = new AppAuthDatabaseStorage($memberId);
70 | $storage->save($localAppAuth);
71 |
72 | $b24->getMainScope()->eventManager()->unbindAllEventHandlers();
73 |
74 | $b24->getMainScope()->eventManager()->bindEventHandlers([
75 | new EventHandlerMetadata(
76 | code: OnApplicationInstall::CODE,
77 | handlerUrl: config('app.url') . '/events/onApplicationInstall',
78 | userId: $currentB24UserId,
79 | ),
80 | new EventHandlerMetadata(
81 | code: OnApplicationUninstall::CODE,
82 | handlerUrl: config('app.url') . '/events/onApplicationUninstall',
83 | userId: $currentB24UserId,
84 | ),
85 | ]);
86 |
87 | // your code here
88 |
89 | return view('b24api.install');
90 | } catch (\Throwable $e) {
91 | return view('b24api.install-fail');
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Http/Middleware/B24AppMiddleware.php:
--------------------------------------------------------------------------------
1 | has('member_id') || $request->has('auth') && isset($request->post('auth')['member_id']) && !empty($request->post('auth')['member_id'])) {
12 | //
13 | } else {
14 | return response()->json(['error' => 'memberId is null'], 406);
15 | }
16 |
17 | return $next($request);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Http/Middleware/B24AppUserMiddleware.php:
--------------------------------------------------------------------------------
1 | post('member_id');
18 | if (empty($memberId)) {
19 | return response()->json(['error' => 'memberId is null'], 406);
20 | }
21 | $reLogin = false;
22 | if (!auth()->check()) {
23 | $reLogin = true;
24 | } elseif ((auth()->user()->getMemberId() != $memberId)) {
25 | $reLogin = true;
26 | } else {
27 | if (is_null(auth()->user()->expires) || time() >= auth()->user()->expires) {
28 | $reLogin = true;
29 | }
30 | }
31 |
32 | if ($reLogin) {
33 | if (!$request->post('AUTH_ID'))
34 | return response()->json(['error' => 'AUTH_ID is null'], 406);
35 |
36 | try {
37 | $b24 = ServiceBuilderFactory::createServiceBuilderFromPlacementRequest(
38 | placementRequest: $request,
39 | applicationProfile: new ApplicationProfile(
40 | clientId: config('bitrix24.client_id'),
41 | clientSecret: config('bitrix24.client_secret'),
42 | scope: Scope::initFromString(config('bitrix24.scope'))
43 | ),
44 | eventDispatcher: new EventDispatcherAdapter(),
45 | logger: resolve('b24log', [
46 | 'memberId' => $memberId,
47 | 'domain' => $request->input('DOMAIN'),
48 | ]),
49 | );
50 |
51 | $profile = $b24->getMainScope()->main()->getCurrentUserProfile()->getUserProfile();
52 |
53 | $userFind = B24User::query()
54 | ->where('user_id', $profile->ID)
55 | ->where('member_id', $memberId)
56 | ->first();
57 |
58 | if ($userFind) {
59 | $userFind->update([
60 | 'access_token' => $request->post('AUTH_ID'),
61 | 'refresh_token' => $request->post('REFRESH_ID'),
62 | 'domain' => $request->get('DOMAIN'),
63 | 'is_admin' => $profile->ADMIN,
64 | 'expires' => time() + (int)$request->post('AUTH_EXPIRES') - 600,
65 | 'expires_in' => 3600,
66 | ]);
67 | } else {
68 | $userFind = B24User::query()
69 | ->create([
70 | 'user_id' => $profile->ID,
71 | 'member_id' => $request->post('member_id'),
72 | 'access_token' => $request->post('AUTH_ID'),
73 | 'refresh_token' => $request->post('REFRESH_ID'),
74 | 'application_token' => $request->post('APP_SID'),
75 | 'domain' => $request->get('DOMAIN'),
76 | 'is_admin' => $profile->ADMIN,
77 | 'expires' => time() + (int)$request->post('AUTH_EXPIRES') - 600,
78 | 'expires_in' => 3600,
79 | ]);
80 | }
81 |
82 | auth()->login($userFind);
83 | if (!auth()->check()) {
84 | return response()->json(['error' => 'Unauthorized, auth failed'], 401);
85 | }
86 | Context::addHidden('memberId', $memberId);
87 | Context::addHidden('userId', $userFind->user_id);
88 | } catch (\Exception $e) {
89 | return response()->json(['error' => $e->getMessage()], 401);
90 | }
91 | }
92 |
93 | return $next($request);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Http/Middleware/B24AuthUserMiddleware.php:
--------------------------------------------------------------------------------
1 | header('X-b24api-member-id');
17 | if (empty($memberId)) {
18 | return response()->json(['error' => 'memberId is null'], 406);
19 | }
20 |
21 | $domain = $request->header('X-b24api-domain');
22 | if (empty($domain)) {
23 | return response()->json(['error' => 'domain is null'], 406);
24 | }
25 |
26 | $accessToken = $request->header('X-b24api-access-token');
27 | if (empty($accessToken)) {
28 | return response()->json(['error' => 'access token is null'], 406);
29 | }
30 |
31 | if (!auth()->check() || (auth()->user()->getMemberId() != $memberId)) {
32 | try {
33 | $applicationProfile = new ApplicationProfile(
34 | clientId: config('bitrix24.client_id'),
35 | clientSecret: config('bitrix24.client_secret'),
36 | scope: Scope::initFromString(config('bitrix24.scope'))
37 | );
38 |
39 | $authToken = new AuthToken(
40 | accessToken: $request->header('X-b24api-access-token'),
41 | refreshToken: $request->header('X-b24api-refresh-token'),
42 | expires: $request->header('X-b24api-expires-in'),
43 | expiresIn: 3600,
44 | );
45 |
46 | $factory = new ServiceBuilderFactory(
47 | eventDispatcher: resolve('userEvents', [
48 | 'memberId' => $memberId,
49 | ]),
50 | log: resolve('b24log', [
51 | 'memberId' => $memberId
52 | ]),
53 | );
54 |
55 | $b24 = $factory->init(
56 | applicationProfile: $applicationProfile,
57 | authToken: $authToken,
58 | bitrix24DomainUrl: "https://{$request->header('X-b24api-domain')}",
59 | );
60 |
61 | $profile = $b24->getUserScope()->user()->current()->user();
62 |
63 | $user = B24User::query()
64 | ->where('member_id', $memberId)
65 | ->where('user_id', $profile->ID)
66 | ->first();
67 |
68 | if (!$user) {
69 | throw new \Exception('User not found');
70 | }
71 |
72 | auth()->login($user);
73 | } catch (\Exception $e) {
74 | return response()->json(['error' => $e->getMessage()], 401);
75 | }
76 | }
77 | return $next($request);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Listeners/PortalDomainUrlChangedListener.php:
--------------------------------------------------------------------------------
1 | where('domain', $event->getOldDomainUrlHost())
20 | ->update([
21 | 'domain' => $event->getNewDomainUrlHost(),
22 | ]);
23 |
24 | B24User::query()
25 | ->where('domain', $event->getOldDomainUrlHost())
26 | ->update([
27 | 'domain' => $event->getNewDomainUrlHost()
28 | ]);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Models/B24App.php:
--------------------------------------------------------------------------------
1 | 'boolean',
32 | ];
33 |
34 | public function getMemberId(){
35 | return $this->member_id;
36 | }
37 |
38 | public function b24app(): BelongsTo
39 | {
40 | return $this->belongsTo(B24App::class, 'member_id', 'member_id');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------