Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
sendgrid_integration
Manage
Activity
Members
Labels
Plan
Wiki
Custom issue tracker
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Model registry
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
project
sendgrid_integration
Merge requests
!13
Issue
#3224250
: Subuser management
Code
Review changes
Check out branch
Download
Patches
Plain diff
Open
Issue
#3224250
: Subuser management
issue/sendgrid_integration-3224250:8.x-2.x
into
8.x-2.x
Overview
0
Commits
1
Pipelines
0
Changes
2
Open
lambic
requested to merge
issue/sendgrid_integration-3224250:8.x-2.x
into
8.x-2.x
3 years ago
Overview
0
Commits
1
Pipelines
0
Changes
2
Expand
0
0
Merge request reports
Compare
8.x-2.x
version 1
8b906ddf
3 years ago
8.x-2.x (HEAD)
and
latest version
latest version
14a89f36
1 commit,
1 year ago
version 1
8b906ddf
1 commit,
3 years ago
2 files
+
590
−
0
Inline
Compare changes
Side-by-side
Inline
Show whitespace changes
Show one file at a time
Files
2
Search (e.g. *.vue) (Ctrl+P)
src/Api.php
0 → 100644
+
577
−
0
Options
<?php
namespace
Drupal\sendgrid_integration
;
use
Drupal\Component\Utility\Xss
;
use
Drupal\Core\Config\ConfigFactoryInterface
;
use
Drupal\Core\Extension\ModuleHandlerInterface
;
use
Drupal\Core\Logger\LoggerChannelFactoryInterface
;
use
Drupal\Core\Messenger\MessengerInterface
;
use
Drupal\Core\Cache\CacheFactoryInterface
;
use
GuzzleHttp\Client
;
use
GuzzleHttp\Exception\ClientException
;
use
\Exception
;
/**
* Class SendGridReportsController.
*
* @package Drupal\sengrid_integration\Controller
*/
class
Api
{
/**
* Api Key of SendGrid.
*
* @var array|mixed|null
*/
protected
$apiKey
=
NULL
;
/**
* Cache bin of SendGrid Reports module.
*
* @var string
*/
protected
$bin
=
'sendgrid_integration'
;
/**
* Include the messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected
$messenger
;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected
$configFactory
;
/**
* Logger service.
*
* @var \Drupal\Core\Logger\LoggerChannelFactory
*/
protected
$loggerFactory
;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected
$moduleHandler
;
/**
* The cache factory service.
*
* @var \Drupal\Core\Cache\CacheFactoryInterface
*/
protected
$cacheFactory
;
/**
* The subuser to perform API calls on behalf of.
*/
protected
$subuser
;
/**
* Api constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger factory.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* The module handler service.
* @param \Drupal\Core\Cache\CacheFactoryInterface $cacheFactory
* The cache factory service.
*/
public
function
__construct
(
ConfigFactoryInterface
$config_factory
,
MessengerInterface
$messenger
,
LoggerChannelFactoryInterface
$logger_factory
,
ModuleHandlerInterface
$moduleHandler
,
CacheFactoryInterface
$cacheFactory
)
{
$this
->
configFactory
=
$config_factory
;
$this
->
messenger
=
$messenger
;
$this
->
loggerFactory
=
$logger_factory
;
$this
->
moduleHandler
=
$moduleHandler
;
$this
->
cacheFactory
=
$cacheFactory
;
// Load key from variables and throw errors if not there.
$key_secret
=
$this
->
configFactory
->
get
(
'sendgrid_integration.settings'
)
->
get
(
'apikey'
);
if
(
$this
->
moduleHandler
->
moduleExists
(
'key'
))
{
$key
=
\Drupal
::
service
(
'key.repository'
)
->
getKey
(
$key_secret
);
if
(
$key
&&
$key
->
getKeyValue
())
{
$this
->
apiKey
=
$key
->
getKeyValue
();
}
}
else
{
$this
->
apiKey
=
$key_secret
;
}
// Display message one time if api key is not set.
if
(
empty
(
$this
->
apiKey
))
{
$this
->
loggerFactory
->
get
(
'sengrid_integration'
)
->
warning
(
t
(
'SendGrid Module is not setup with API key.'
));
$this
->
messenger
->
addWarning
(
'Sendgrid Module is not setup with an API key.'
);
}
}
/**
* Set the subuser to perform API calls on behalf of.
*
* @param string $subuser
* A valid subuser.
*
* @return \Drupal\sendgrid_integration\Api
* This object.
*/
public
function
setSubuser
(
string
$subuser
):
Api
{
$this
->
subuser
=
$subuser
;
return
$this
;
}
/**
* Sets the cache to sengrid_integration bin.
*
* @param string $cid
* Cache Id.
* @param array $data
* The data should be cached.
*/
protected
function
setCache
(
$cid
,
array
$data
)
{
if
(
!
empty
(
$data
))
{
$this
->
cacheFactory
->
get
(
$this
->
bin
)
->
set
(
$cid
,
$data
);
}
}
/**
* Get the guzzle client.
*
* @param bool $parent
* True if we want to use the parent user for this request.
*
* @return \Guzzlehttp\Client
* The Guzzle client object.
*/
protected
function
getClient
(
$parent
=
FALSE
)
{
$headers
[
'Authorization'
]
=
'Bearer '
.
$this
->
apiKey
;
if
(
$this
->
subuser
&&
!
$parent
)
{
$headers
[
'on-behalf-of'
]
=
$this
->
subuser
;
}
$client
=
new
Client
([
'base_uri'
=>
'https://api.sendgrid.com/v3/'
,
'headers'
=>
$headers
,
]);
return
$client
;
}
/**
* Get request to SendGrid.
*
* @param string $path
* Part of SendGrid endpoint.
* @param array $query
* Query params to the request.
* @param bool $parent
* Perform the request as the parent user.
*
* @return bool|mixed
* Decoded json or FALSE.
*/
protected
function
get
(
$path
,
array
$query
=
[],
$parent
=
FALSE
)
{
$client
=
$this
->
getClient
(
$parent
);
// Lets attempt the request and catch an error if it fails.
try
{
$response
=
$client
->
get
(
$path
,
[
'query'
=>
$query
]);
}
catch
(
ClientException
$e
)
{
$code
=
Xss
::
filter
(
$e
->
getCode
());
devel_debug
(
$code
);
$this
->
loggerFactory
->
get
(
'sengrid_integration'
)
->
error
(
t
(
'SendGrid module failed to receive data. HTTP Error Code @errno'
,
[
'@errno'
=>
$code
]));
$this
->
messenger
->
addError
(
t
(
'SendGrid module failed to receive data. See logs.'
));
return
FALSE
;
}
// Sanitize return before using in Drupal.
$body
=
Xss
::
filter
(
$response
->
getBody
());
return
json_decode
(
$body
);
}
/**
* Post request to SendGrid.
*
* @param string $path
* Part of SendGrid endpoint.
* @param array $data
* Query params to the request.
* @param bool $parent
* Perform the request as the parent user.
*
* @return bool|mixed
* Decoded json or FALSE.
*/
protected
function
post
(
$path
,
array
$data
,
$parent
=
FALSE
)
{
$client
=
$this
->
getClient
(
$parent
);
// Lets attempt the request and catch an error if it fails.
try
{
$response
=
$client
->
post
(
$path
,
[
'json'
=>
$data
]);
}
catch
(
ClientException
$e
)
{
$code
=
Xss
::
filter
(
$e
->
getCode
());
$this
->
loggerFactory
->
get
(
'sengrid_integration'
)
->
error
(
t
(
'SendGrid module failed to post data. HTTP Error Code @errno'
,
[
'@errno'
=>
$code
]));
$this
->
messenger
->
addError
(
t
(
'SendGrid module failed to post data. See logs.'
));
return
FALSE
;
}
// Sanitize return before using in Drupal.
$body
=
Xss
::
filter
(
$response
->
getBody
());
return
json_decode
(
$body
);
}
/**
* Delete request to SendGrid.
*
* @param string $path
* Part of SendGrid endpoint.
* @param string $data
* The id of the item to be deleted.
* @param bool $parent
* Perform the request as the parent user.
*
* @return bool|mixed
* Decoded json or FALSE.
*/
protected
function
delete
(
$path
,
string
$data
,
$parent
=
FALSE
)
{
$client
=
$this
->
getClient
(
$parent
);
// Lets attempt the request and catch an error if it fails.
try
{
$response
=
$client
->
delete
(
$path
.
'/'
.
$data
);
}
catch
(
ClientException
$e
)
{
$code
=
Xss
::
filter
(
$e
->
getCode
());
$this
->
loggerFactory
->
get
(
'sengrid_integration'
)
->
error
(
t
(
'SendGrid module failed to receive data. HTTP Error Code @errno'
,
[
'@errno'
=>
$code
]));
$this
->
messenger
->
addError
(
t
(
'SendGrid module failed to receive data. See logs.'
));
return
FALSE
;
}
// Sanitize return before using in Drupal.
$body
=
Xss
::
filter
(
$response
->
getBody
());
return
json_decode
(
$body
);
}
/**
* Get subuser info for the passed username.
*
* @param string $username
* A string to search usernames for.
*
* @return array
* Data relating to the subuser.
*/
public
function
getSubUser
(
string
$username
):
array
{
$data
=
[];
$data
[
'username'
]
=
$username
;
$response
=
$this
->
get
(
'subusers'
,
$data
);
return
$response
;
}
/**
* Create a subuser.
*
* @param string $username
* The username of the subuser being created.
* @param string $email
* A valid email address for the subuser.
* @param string $password
* The subuser password.
* @param array $ips
* An array of IP addresses to associated with the user.
* If this is not passed, the least currently used IP will be used.
*
* @return array
* Response from sendgrid.
*/
public
function
createSubUser
(
string
$username
,
string
$email
,
string
$password
,
array
$ips
=
[]):
array
{
$existing
=
$this
->
getSubUser
(
$username
);
foreach
(
$existing
as
$subuser
)
{
if
(
$subuser
->
username
==
$username
)
{
// @todo use custom exception.
throw
new
Exception
(
'Username already exists: '
.
$username
);
}
}
if
(
!
$ips
)
{
$ips
=
[
$this
->
getLeastUsedIp
()];
}
$data
=
[
'username'
=>
$username
,
'email'
=>
$email
,
'password'
=>
$password
,
'ips'
=>
$ips
,
];
$response
=
$this
->
post
(
'subusers'
,
$data
,
TRUE
);
// Response from sendgrid doesn't give the IPs, so add it here.
if
(
is_object
(
$response
))
{
$response
->
ips
=
$ips
;
}
devel_debug
(
$response
);
return
(
array
)
$response
;
}
/**
* Delete a subuser.
*
* @param string $username
* The username of the subuser being deleted.
*/
public
function
deleteSubuser
(
string
$username
)
{
$existing
=
$this
->
getSubUser
(
$username
);
$response
=
FALSE
;
foreach
(
$existing
as
$subuser
)
{
if
(
$subuser
->
username
==
$username
)
{
$response
=
$this
->
delete
(
'subusers'
,
$subuser
->
username
);
}
}
if
(
$response
===
FALSE
)
{
throw
new
Exception
(
'Subuser not found.'
);
}
}
/**
* Create an API key.
*
* @param $name
* A name for the API key.
* @param $perms
* The perms to assign to this API key. NULL means full access.
*
* @return array
* Response from sendgrid.
*/
public
function
createApiKey
(
$name
,
$perms
=
NULL
):
array
{
if
(
!
$this
->
user
)
{
// @todo use a custom exception.
throw
new
Exception
(
'Attempt to create an API key without a subuser set.'
);
}
if
(
!
$perms
)
{
$perms
=
$this
->
fullAccess
();
}
$data
=
[
'name'
=>
$name
,
'scopes'
=>
$perms
,
];
return
$this
->
post
(
'api_keys'
,
$data
);
}
/**
* Get a list of valid IP Addresses.
*
* @param array $mapping
* An optional array of mappings to use to filter the ip list.
*
* @return array
* Array of valid IP addresses.
*/
public
function
getIpAddresses
(
array
$mappings
=
[]):
array
{
$ips
=
$this
->
get
(
'ips'
);
if
(
!
empty
(
$mapping
))
{
foreach
(
$ips
as
$key
=>
$ip
)
{
if
(
!
isset
(
$mapping
[
$ip
->
ip
]))
{
unset
(
$ips
[
$key
]);
}
}
}
return
$ips
;
}
/**
* Get the IP address with the least number of associated subusers.
*
* @param array $mapping
* An optional array of mappings to use to filter the ip list.
*
* @return string
* The IP address with the least associated subusers.
*/
public
function
getLeastUsedIp
(
array
$mappings
=
[]):
string
{
$ips
=
$this
->
getIpAddresses
(
$mappings
);
$least
=
$this
->
least
(
$ips
,
'ip'
);
return
$least
;
}
/**
* Get a list of available domains.
*
* @return array
* Array of valid send domains.
*/
public
function
getDomains
()
{
$domains
=
$this
->
get
(
'whitelabel/domains'
,
NULL
,
TRUE
);
return
$domains
;
}
/**
* Get the domain with the least number of associated subusers.
*
* @return string
* The domain with the least associated subusers.
*/
public
function
getLeastUsedDomain
()
{
$domains
=
$this
->
getDomains
();
$least
=
$this
->
least
(
$domains
,
'id'
);
return
$least
;
}
/**
* Associated a domain with a subuser.
*
* @param $domain
* Domain to associate. Least used domain will be picked if this is empty.
*/
public
function
associateDomain
(
$domain
=
''
)
{
if
(
!
$this
->
user
)
{
throw
new
Exception
(
'Attempt to associated a domain without a subuser set.'
);
}
$domain
=
$domain
??
$this
->
getLeastUsedDomain
();
$data
=
[
'username'
=>
$this
->
user
];
$response
=
$this
->
post
(
'whitelabel/domains/'
.
$domain
.
'/subuser'
,
$data
,
TRUE
);
return
$response
;
}
/**
* Get a list of available link brandings.
*
* @return array
* Array of available branding links.
*/
public
function
getLinks
()
{
$links
=
$this
->
get
(
'whitelabel/links'
);
return
$links
;
}
/**
* Get the link with the least number of associated subusers.
*
* @return string
* Link with the least associated subusers.
*/
public
function
getLeastUsedLink
()
{
$links
=
$this
->
getLinks
();
$least
=
$this
->
least
(
$links
,
'id'
);
return
$least
;
}
/**
* Associated a link with a subuser.
*/
public
function
associateLink
(
$link
)
{
if
(
!
$this
->
user
)
{
throw
new
Exception
(
'Attempt to associated a link without a subuser set.'
);
}
$link
=
$link
??
$this
->
getLeastUsedLink
();
$data
=
[
'username'
=>
$this
->
user
];
$response
=
$this
->
post
(
'whitelabel/links/'
.
$link
.
'/subuser'
,
$data
,
TRUE
);
return
$response
;
}
/**
* Helper function to find the least number of subusers in an array.
*
* @param array $list
* A list of objects as returned from the Sendgrid API.
* @param string $return
* The name of the objectproperty to return.
*
* @return string
* The value of $return for the least associated subusers.
*/
private
function
least
(
$list
,
$return
)
{
$prev
=
0
;
$least
=
''
;
foreach
(
$list
as
$row
)
{
$count
=
count
(
$row
->
subusers
);
if
(
!
$prev
||
$count
<
$prev
)
{
$prev
=
$count
;
$least
=
$row
->
$return
;
}
}
return
$least
;
}
/**
* Helper functions for api key scopes.
*/
private
function
fullAccess
()
{
return
[
"alerts.create"
,
"alerts.read"
,
"alerts.update"
,
"alerts.delete"
,
"ips.warmup.create"
,
"ips.warmup.read"
,
"ips.warmup.update"
,
"ips.warmup.delete"
,
"ips.pools.create"
,
"ips.pools.read"
,
"ips.pools.update"
,
"ips.pools.delete"
,
"ips.pools.ips.create"
,
"ips.pools.ips.read"
,
"ips.pools.ips.update"
,
"ips.pools.ips.delete"
,
"ips.read"
,
"mail.send"
,
"mail_settings.bcc.read"
,
"mail_settings.address_whitelist.read"
,
"mail_settings.footer.read"
,
"mail_settings.forward_spam.read"
,
"mail_settings.plain_content.read"
,
"mail_settings.spam_check.read"
,
"mail_settings.bounce_purge.read"
,
"mail_settings.forward_bounce.read"
,
"tracking_settings.click.read"
,
"tracking_settings.subscription.read"
,
"tracking_settings.open.read"
,
"tracking_settings.google_analytics.read"
,
"stats.read"
,
"stats.global.read"
,
"categories.stats.read"
,
"categories.stats.sums.read"
,
"devices.stats.read"
,
"clients.stats.read"
,
"clients.phone.stats.read"
,
"clients.tablet.stats.read"
,
"clients.webmail.stats.read"
,
"clients.desktop.stats.read"
,
"geo.stats.read"
,
"mailbox_providers.stats.read"
,
"browsers.stats.read"
,
"user.webhooks.parse.stats.read"
,
"api_keys.read"
,
"categories.create"
,
"categories.read"
,
"categories.update"
,
"categories.delete"
,
"mail.batch.create"
,
"mail.batch.read"
,
"mail.batch.update"
,
"mail.batch.delete"
,
"access_settings.whitelist.create"
,
"access_settings.whitelist.read"
,
"access_settings.whitelist.update"
,
"access_settings.whitelist.delete"
,
"access_settings.activity.read"
,
"whitelabel.create"
,
"whitelabel.read"
,
"whitelabel.update"
,
"whitelabel.delete"
,
"suppression.create"
,
"suppression.read"
,
"suppression.update"
,
"suppression.delete"
,
];
}
}
Loading