Skip to content

Commit

Permalink
Merge pull request #10 from rapid7/unshorten
Browse files Browse the repository at this point in the history
Unshorten: Open-source plugin
  • Loading branch information
jschipp-r7 authored Jun 12, 2019
2 parents b2203d8 + 9db4c1c commit 391b387
Show file tree
Hide file tree
Showing 21 changed files with 460 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ The following table shows which plugins are officially supported by Rapid7 devel
| type_converter | rapid7 |
| typo_squatter | community |
| uniq | community |
| unshorten | community |
| uuid | community |
| virustotal_yara | community |
| vmray | community |
Expand Down
20 changes: 20 additions & 0 deletions unshorten/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM komand/python-pypy3-plugin:2
LABEL organization=komand
LABEL sdk=python
LABEL type=plugin

ENV SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_DIR /etc/ssl/certs
ENV REQUESTS_CA_BUNDLE /etc/ssl/certs/ca-certificates.crt

ADD ./plugin.spec.yaml /plugin.spec.yaml
ADD . /python/src

WORKDIR /python/src
# Add any package dependencies here

# End package dependencies
RUN if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
RUN python setup.py build && python setup.py install

ENTRYPOINT ["/usr/local/bin/komand_unshorten"]
53 changes: 53 additions & 0 deletions unshorten/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Include other Makefiles for improved functionality
INCLUDE_DIR = ../tools/Makefiles
MAKEFILES := $(wildcard $(INCLUDE_DIR)/*.mk)
# We can't guarantee customers will have the include files
# - prefix to ignore Makefiles when not present
# https://www.gnu.org/software/make/manual/html_node/Include.html
-include $(MAKEFILES)

ifneq ($(MAKEFILES),)
$(info [$(YELLOW)*$(NORMAL)] Use ``make menu`` for available targets)
$(info [$(YELLOW)*$(NORMAL)] Including available Makefiles: $(MAKEFILES))
$(info --)
else
$(warning Makefile includes directory not present: $(INCLUDE_DIR))
endif

VERSION?=$(shell grep '^version: ' plugin.spec.yaml | sed 's/version: //')
NAME?=$(shell grep '^name: ' plugin.spec.yaml | sed 's/name: //')
VENDOR?=$(shell grep '^vendor: ' plugin.spec.yaml | sed 's/vendor: //')
CWD?=$(shell basename $(PWD))
_NAME?=$(shell echo $(NAME) | awk '{ print toupper(substr($$0,1,1)) tolower(substr($$0,2)) }')
PKG=$(VENDOR)-$(NAME)-$(VERSION).tar.gz

# Set default target explicitly. Make's default behavior is the first target in the Makefile.
# We don't want that behavior due to includes which are read first
.DEFAULT_GOAL := default # Make >= v3.80 (make -version)


default: image tarball

tarball:
$(info [$(YELLOW)*$(NORMAL)] Creating plugin tarball)
rm -rf build
rm -rf $(PKG)
tar -cvzf $(PKG) --exclude=$(PKG) --exclude=tests --exclude=run.sh *

image:
$(info [$(YELLOW)*$(NORMAL)] Building plugin image)
docker build --pull -t $(VENDOR)/$(NAME):$(VERSION) .
docker tag $(VENDOR)/$(NAME):$(VERSION) $(VENDOR)/$(NAME):latest

regenerate:
$(info [$(YELLOW)*$(NORMAL)] Regenerating schema from plugin.spec.yaml)
icon-plugin generate python --regenerate

export: image
$(info [$(YELLOW)*$(NORMAL)] Exporting docker image)
@printf "\n ---> Exporting Docker image to ./$(VENDOR)_$(NAME)_$(VERSION).tar\n"
@docker save $(VENDOR)/$(NAME):$(VERSION) | gzip > $(VENDOR)_$(NAME)_$(VERSION).tar

# Make will not run a target if a file of the same name exists unless setting phony targets
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONY: default tarball image regenerate
32 changes: 32 additions & 0 deletions unshorten/bin/komand_unshorten
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env python
# GENERATED BY KOMAND SDK - DO NOT EDIT
import komand
from komand_unshorten import connection, actions, triggers


Name = 'Unshorten.me'
Vendor = 'rapid7'
Version = '1.0.0'
Description = 'Unshorten.me provides an easy method to unshorten a wide range of shortened URLs'


class ICONUnshorten(komand.Plugin):
def __init__(self):
super(self.__class__, self).__init__(
name=Name,
vendor=Vendor,
version=Version,
description=Description,
connection=connection.Connection()
)
self.add_action(actions.Unshorten())


def main():
"""Run plugin"""
cli = komand.CLI(ICONUnshorten())
cli.run()


if __name__ == "__main__":
main()
87 changes: 87 additions & 0 deletions unshorten/help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@

# Unshorten.me

## About

[Unshorten.me](https://unshorten.me/) is a free service which unshorten's a wide range of shortened URLs.

This plugin utilizes the [Unshorten.me API](https://unshorten.me/api).

## Actions

### Unshorten

This action is used to unshorten a URL.

#### Input

|Name|Type|Default|Required|Description|Enum|
|----|----|-------|--------|-----------|----|
|url|string|None|True|Short URL|None|

#### Output

|Name|Type|Required|Description|
|----|----|--------|-----------|
|resolved_url|string|True|Long URL|
|success|boolean|True|Success|
|usage_count|integer|True|Usage count|
|requested_url|string|True|Short URL|
|error|string|False|Error message|

Example output:

```
{
"requested_url": "https://bit.ly/1dNVPAW",
"resolved_url": "http://www.google.com/",
"success": true,
"usage_count": 5
}
```

An error can occur from the Unshorten API e.g. submitting a malformed URL. Any error messages from the API are contained in a key called `error`.
In addition, `success` will be set to `false`.

Example output:

```
{
"requested_url": "https://adfasdfadsfadsfasdfadsf.netadfa",
"resolved_url": "",
"error": "Connection Error",
"success": false,
"usage_count": 0
}
```

## Triggers

This plugin does not contain any triggers.

## Connection

This plugin does not contain a connection.

## Troubleshooting

Note that the API is limited to 10 requests per hour per IP address.

## Versions

* 1.0.0 - Initial plugin

## Workflows

Examples:

* Phishing campaigns

## References

* [Unshorten.me](https://unshorten.me/)
* [API](https://unshorten.me/)
Binary file added unshorten/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions unshorten/komand_unshorten/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# GENERATED BY KOMAND SDK - DO NOT EDIT
2 changes: 2 additions & 0 deletions unshorten/komand_unshorten/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# GENERATED BY KOMAND SDK - DO NOT EDIT
from .unshorten.action import Unshorten
2 changes: 2 additions & 0 deletions unshorten/komand_unshorten/actions/unshorten/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# GENERATED BY KOMAND SDK - DO NOT EDIT
from .action import Unshorten
53 changes: 53 additions & 0 deletions unshorten/komand_unshorten/actions/unshorten/action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import komand
from .schema import UnshortenInput, UnshortenOutput
# Custom imports below
import requests


class Unshorten(komand.Action):

def __init__(self):
super(self.__class__, self).__init__(
name='unshorten',
description='Unshorten a shortened URL',
input=UnshortenInput(),
output=UnshortenOutput())

def run(self, params={}):
short_url = params.get('url')
try:
r = requests.get('https://unshorten.me/json/' + short_url)
r.raise_for_status()
out = r.json()
except Exception as e:
self.logger.error(e)
raise

try:
if out['error']:
self.logger.error(out.get('error'))
except KeyError:
# All good, no error key is present
self.logger.info('No errors')

return out

def test(self):
url = 'https://bit.ly/komand_rocks'
try:
r = requests.get('https://unshorten.me/json/' + url)
r.raise_for_status()
out = r.json()
except Exception as e:
self.logger.error(e)
raise

# All good
try:
if out['error']:
self.logger.error(out.get('error'))
except KeyError:
# All good, no error key is present
self.logger.info('No errors')

return out
88 changes: 88 additions & 0 deletions unshorten/komand_unshorten/actions/unshorten/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# GENERATED BY KOMAND SDK - DO NOT EDIT
import komand
import json


class Input:
URL = "url"


class Output:
ERROR = "error"
REQUESTED_URL = "requested_url"
RESOLVED_URL = "resolved_url"
SUCCESS = "success"
USAGE_COUNT = "usage_count"


class UnshortenInput(komand.Input):
schema = json.loads("""
{
"type": "object",
"title": "Variables",
"properties": {
"url": {
"type": "string",
"title": "URL",
"description": "Short URL",
"order": 1
}
},
"required": [
"url"
]
}
""")

def __init__(self):
super(self.__class__, self).__init__(self.schema)


class UnshortenOutput(komand.Output):
schema = json.loads("""
{
"type": "object",
"title": "Variables",
"properties": {
"error": {
"type": "string",
"title": "Error",
"description": "Error message",
"order": 5
},
"requested_url": {
"type": "string",
"title": "Requested URL",
"description": "Short URL",
"order": 1
},
"resolved_url": {
"type": "string",
"title": "Resolved URL",
"description": "Long URL",
"order": 2
},
"success": {
"type": "boolean",
"title": "Success",
"description": "Success",
"order": 3
},
"usage_count": {
"type": "integer",
"title": "Usage Count",
"description": "Usage count",
"order": 4
}
},
"required": [
"usage_count",
"requested_url",
"resolved_url",
"success"
]
}
""")

def __init__(self):
super(self.__class__, self).__init__(self.schema)
2 changes: 2 additions & 0 deletions unshorten/komand_unshorten/connection/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# GENERATED BY KOMAND SDK - DO NOT EDIT
from .connection import Connection
12 changes: 12 additions & 0 deletions unshorten/komand_unshorten/connection/connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import komand
from .schema import ConnectionSchema
# Custom imports below


class Connection(komand.Connection):

def __init__(self):
super(self.__class__, self).__init__(input=ConnectionSchema())

def connect(self, params):
pass
15 changes: 15 additions & 0 deletions unshorten/komand_unshorten/connection/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# GENERATED BY KOMAND SDK - DO NOT EDIT
import komand
import json


class Input:
pass

class ConnectionSchema(komand.Input):
schema = json.loads("""
{}
""")

def __init__(self):
super(self.__class__, self).__init__(self.schema)
1 change: 1 addition & 0 deletions unshorten/komand_unshorten/triggers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# GENERATED BY KOMAND SDK - DO NOT EDIT
1 change: 1 addition & 0 deletions unshorten/komand_unshorten/util/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# GENERATED BY KOMAND SDK - DO NOT EDIT
Loading

0 comments on commit 391b387

Please sign in to comment.