Beispiel #1
0
async def send(conn, batch_list, timeout, webhook=False):
    """Send batch_list to sawtooth."""
    batch_request = client_batch_submit_pb2.ClientBatchSubmitRequest()
    batch_request.batches.extend(list(batch_list.batches))
    validator_response = await conn.send(
        validator_pb2.Message.CLIENT_BATCH_SUBMIT_REQUEST,
        batch_request.SerializeToString(),
        timeout,
    )
    client_response = client_batch_submit_pb2.ClientBatchSubmitResponse()
    client_response.ParseFromString(validator_response.content)
    status = client_response.status

    if not webhook:
        if status == client_batch_submit_pb2.ClientBatchSubmitResponse.INTERNAL_ERROR:
            raise ApiInternalError("Internal Error")
        elif status == client_batch_submit_pb2.ClientBatchSubmitResponse.INVALID_BATCH:
            raise ApiBadRequest("Invalid Batch")
        elif status == client_batch_submit_pb2.ClientBatchSubmitResponse.QUEUE_FULL:
            raise ApiInternalError("Queue Full")
    elif status != client_batch_submit_pb2.ClientBatchSubmitResponse.OK:
        return None

    status_request = client_batch_submit_pb2.ClientBatchStatusRequest()
    status_request.batch_ids.extend(
        list(b.header_signature for b in batch_list.batches))
    status_request.wait = True
    status_request.timeout = timeout
    validator_response = await conn.send(
        validator_pb2.Message.CLIENT_BATCH_STATUS_REQUEST,
        status_request.SerializeToString(),
        timeout,
    )
    status_response = client_batch_submit_pb2.ClientBatchStatusResponse()
    status_response.ParseFromString(validator_response.content)
    status = status_response.status

    if not webhook:
        if status != client_batch_submit_pb2.ClientBatchStatusResponse.OK:
            raise ApiInternalError("Internal Error")
    elif status != client_batch_submit_pb2.ClientBatchStatusResponse.OK:
        return None

    response = status_response.batch_statuses[0]
    status = response.status

    if not webhook:
        if status == client_batch_submit_pb2.ClientBatchStatus.INVALID:
            raise ApiBadRequest("Bad Request: {}".format(
                response.invalid_transactions[0].message))
        elif status == client_batch_submit_pb2.ClientBatchStatus.PENDING:
            raise ApiInternalError("Internal Error: Transaction timed out.")
        elif status == client_batch_submit_pb2.ClientBatchStatus.UNKNOWN:
            raise ApiInternalError("Internal Error: Unspecified error.")
    return status
async def send(conn, batch_list, timeout):
    batch_request = client_batch_submit_pb2.ClientBatchSubmitRequest()
    batch_request.batches.extend(list(batch_list.batches))

    validator_response = await conn.send(
        validator_pb2.Message.CLIENT_BATCH_SUBMIT_REQUEST,
        batch_request.SerializeToString(),
        timeout,
    )

    client_response = client_batch_submit_pb2.ClientBatchSubmitResponse()
    client_response.ParseFromString(validator_response.content)

    if (client_response.status ==
            client_batch_submit_pb2.ClientBatchSubmitResponse.INTERNAL_ERROR):
        raise ApiInternalError("Internal Error")
    elif (client_response.status ==
          client_batch_submit_pb2.ClientBatchSubmitResponse.INVALID_BATCH):
        raise ApiBadRequest("Invalid Batch")
    elif (client_response.status ==
          client_batch_submit_pb2.ClientBatchSubmitResponse.QUEUE_FULL):
        raise ApiInternalError("Queue Full")

    status_request = client_batch_submit_pb2.ClientBatchStatusRequest()
    status_request.batch_ids.extend(
        list(b.header_signature for b in batch_list.batches))
    status_request.wait = True
    status_request.timeout = timeout

    validator_response = await conn.send(
        validator_pb2.Message.CLIENT_BATCH_STATUS_REQUEST,
        status_request.SerializeToString(),
        timeout,
    )

    status_response = client_batch_submit_pb2.ClientBatchStatusResponse()
    status_response.ParseFromString(validator_response.content)

    if status_response.status != client_batch_submit_pb2.ClientBatchStatusResponse.OK:
        raise ApiInternalError("Internal Error")

    resp = status_response.batch_statuses[0]

    if resp.status == client_batch_submit_pb2.ClientBatchStatus.COMMITTED:
        return resp
    elif resp.status == client_batch_submit_pb2.ClientBatchStatus.INVALID:
        raise ApiBadRequest("Bad Request: {}".format(
            resp.invalid_transactions[0].message))
    elif resp.status == client_batch_submit_pb2.ClientBatchStatus.PENDING:
        raise ApiInternalError(
            "Internal Error: Transaction submitted but timed out.")
    elif resp.status == client_batch_submit_pb2.ClientBatchStatus.UNKNOWN:
        raise ApiInternalError(
            "Internal Error: Something went wrong. Try again later.")
async def fetch_latest_block(conn):
    try:
        return (await r.table("blocks").max(index="block_num").merge({
            "id":
            r.row["block_id"],
            "num":
            r.row["block_num"]
        }).without("block_id", "block_num").run(conn))
    except ReqlNonExistenceError:
        raise ApiInternalError("Internal Error: No block data found in state")
async def create_new_role(request):
    """Create a new role."""
    log_request(request)
    env = Env()
    if not env.int("ENABLE_NEXT_BASE_USE"):
        raise ApiDisabled("Not a valid action. Source not enabled.")
    required_fields = ["name", "administrators", "owners"]
    validate_fields(required_fields, request.json)
    role_title = " ".join(request.json.get("name").split())
    conn = await create_connection()
    response = await roles_query.roles_search_duplicate(conn, role_title)
    conn.close()
    if not response:
        txn_key, txn_user_id = await get_transactor_key(request)
        role_id = str(uuid4())

        if request.json.get("metadata") is None:
            set_metadata = {}
        else:
            set_metadata = request.json.get("metadata")
        set_metadata["sync_direction"] = "OUTBOUND"
        batch_list = Role().batch_list(
            signer_keypair=txn_key,
            signer_user_id=txn_user_id,
            name=role_title,
            role_id=role_id,
            metadata=set_metadata,
            admins=request.json.get("administrators"),
            owners=request.json.get("owners"),
            description=request.json.get("description"),
        )
        sawtooth_response = await send(
            request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT
        )

        if not sawtooth_response:
            LOGGER.warning("There was an error submitting the sawtooth transaction.")
            return await handle_errors(
                request,
                ApiInternalError(
                    "There was an error submitting the sawtooth transaction."
                ),
            )

        return create_role_response(request, role_id)
    return await handle_errors(
        request,
        ApiTargetConflict(
            "Error: Could not create this role because the role name already exists."
        ),
    )
async def fetch_latest_block_with_retry(conn, tries=5):
    attempts = tries
    while attempts > 0:
        try:
            return await fetch_latest_block(conn)
        except ReqlQueryLogicError:
            LOGGER.debug("Query attempt failed")

        sleep_time = (tries - attempts + 1) * 2
        LOGGER.debug("Retrying in %s secs", sleep_time)
        time.sleep(sleep_time)

        attempts -= 1

    raise ApiInternalError("Internal Error: No block data found in state")
Beispiel #6
0
async def check_admin_status(next_id):
    """Verfiy that a user is a member of NEXT admins.  Return boolean.
    Args:
        next_id:
            str: user's next_id
    """
    conn = await create_connection()
    admin_role = await get_role_by_name(conn, "NextAdmins")
    if not admin_role:
        raise ApiInternalError(
            "NEXT administrator group has not been created.")
    admin_membership = await get_role_membership(conn, next_id,
                                                 admin_role[0]["role_id"])
    conn.close()
    if admin_membership:
        return True
    return False
Beispiel #7
0
async def create_new_user(request):
    """Create a new user. Must be an adminsitrator.

    Args:
        request:
            obj: incoming request object
    """
    log_request(request, True)
    # Validate that we have all fields
    required_fields = ["name", "username", "password", "email"]
    validate_fields(required_fields, request.json)
    # Check if username already exists
    conn = await create_connection()
    username = request.json.get("username")
    if await users_query.fetch_username_match_count(conn, username) > 0:
        # Throw Error response to Next_UI
        return await handle_errors(
            request, ApiTargetConflict("Username already exists."))
    conn.close()

    # Check to see if they are trying to create the NEXT admin
    env = Env()
    next_admin = {
        "name": env("NEXT_ADMIN_NAME"),
        "username": env("NEXT_ADMIN_USER"),
        "email": env("NEXT_ADMIN_EMAIL"),
        "password": env("NEXT_ADMIN_PASS"),
    }
    if request.json != next_admin:
        # Try to see if they are in NEXT
        if not env.int("ENABLE_NEXT_BASE_USE"):
            raise ApiDisabled("Not a valid action. Source not enabled.")
        txn_key, txn_user_id, next_id, key_pair = await non_admin_creation(
            request)
    else:
        txn_key, txn_user_id, next_id, key_pair = await next_admin_creation(
            request)
    if request.json.get("metadata") is None:
        set_metadata = {}
    else:
        set_metadata = request.json.get("metadata")
    set_metadata["sync_direction"] = "OUTBOUND"
    # Build create user transaction
    batch_list = User().batch_list(
        signer_keypair=txn_key,
        signer_user_id=txn_user_id,
        next_id=next_id,
        name=request.json.get("name"),
        username=request.json.get("username"),
        email=request.json.get("email"),
        metadata=set_metadata,
        manager_id=request.json.get("manager"),
        key=key_pair.public_key,
    )

    # Submit transaction and wait for complete
    sawtooth_response = await send(request.app.config.VAL_CONN, batch_list,
                                   request.app.config.TIMEOUT)
    if not sawtooth_response:
        return await handle_errors(
            request,
            ApiInternalError(
                "There was an error submitting the sawtooth transaction."),
        )

    # Save new user in auth table
    salt = hashlib.sha256(os.urandom(60)).hexdigest().encode("utf-8")
    password = request.json.get("password").encode("utf-8")
    hashed_password = hashlib.pbkdf2_hmac("sha256", password, salt,
                                          100000).hex()

    encrypted_private_key = encrypt_private_key(AES_KEY, key_pair.public_key,
                                                key_pair.private_key_bytes)
    auth_entry = {
        "next_id": next_id,
        "salt": salt,
        "hashed_password": hashed_password,
        "encrypted_private_key": encrypted_private_key,
        "username": request.json.get("username"),
        "email": request.json.get("email"),
    }

    mapping_data = {
        "next_id": next_id,
        "provider_id": "NEXT-created",
        "remote_id": None,
        "public_key": key_pair.public_key,
        "encrypted_key": encrypted_private_key,
        "active": True,
    }

    # Insert to user_mapping and close
    await auth_query.create_auth_entry(auth_entry)
    conn = await create_connection()
    await users_query.create_user_map_entry(conn, mapping_data)
    conn.close()

    # Send back success response
    return json({"data": {"user": {"id": next_id}}})
Beispiel #8
0
async def create_new_user(request):
    """Create a new user."""
    env = Env()
    admin_role = {
        "name": env("NEXT_ADMIN_NAME"),
        "username": env("NEXT_ADMIN_USER"),
        "email": env("NEXT_ADMIN_EMAIL"),
        "password": env("NEXT_ADMIN_PASS"),
    }
    if request.json != admin_role:
        if not env.int("ENABLE_NEXT_BASE_USE"):
            raise ApiBadRequest("Not a valid action. Source not enabled")
    required_fields = ["name", "username", "password", "email"]
    utils.validate_fields(required_fields, request.json)
    username_created = request.json.get("username")
    # Check if username already exists
    if (await users_query.fetch_username_match_count(
            request.app.config.DB_CONN, username_created) > 0):
        # Throw Error response to Next_UI
        return await handle_errors(
            request,
            ApiTargetConflict(
                "Username already exists. Please give a different Username."),
        )

    # Generate keys
    key_pair = Key()
    next_id = str(uuid4())
    encrypted_private_key = encrypt_private_key(AES_KEY, key_pair.public_key,
                                                key_pair.private_key_bytes)
    if request.json.get("metadata") is None or request.json.get(
            "metadata") == {}:
        set_metadata = {}
    else:
        set_metadata = request.json.get("metadata")
    set_metadata["sync_direction"] = "OUTBOUND"

    # Build create user transaction
    batch_list = User().batch_list(
        signer_keypair=key_pair,
        signer_user_id=next_id,
        next_id=next_id,
        name=request.json.get("name"),
        username=request.json.get("username"),
        email=request.json.get("email"),
        metadata=set_metadata,
        manager_id=request.json.get("manager"),
        key=key_pair.public_key,
    )

    # Submit transaction and wait for complete
    sawtooth_response = await utils.send(request.app.config.VAL_CONN,
                                         batch_list,
                                         request.app.config.TIMEOUT)

    if not sawtooth_response:
        LOGGER.warning(
            "There was an error submitting the sawtooth transaction.")
        return await handle_errors(
            request,
            ApiInternalError(
                "There was an error submitting the sawtooth transaction."),
        )

    # Save new user in auth table
    hashed_password = hashlib.sha256(
        request.json.get("password").encode("utf-8")).hexdigest()

    auth_entry = {
        "next_id": next_id,
        "hashed_password": hashed_password,
        "encrypted_private_key": encrypted_private_key,
        "username": request.json.get("username"),
        "email": request.json.get("email"),
    }

    mapping_data = {
        "next_id": next_id,
        "provider_id": "NEXT-created",
        "remote_id": None,
        "public_key": key_pair.public_key,
        "encrypted_key": encrypted_private_key,
        "active": True,
    }

    # Insert to user_mapping and close
    await auth_query.create_auth_entry(auth_entry)
    await users_query.create_user_map_entry(request.app.config.DB_CONN,
                                            mapping_data)

    # Send back success response
    return create_user_response(request, next_id)
Beispiel #9
0
async def create_new_role(request):
    """Create a new role."""
    required_fields = ["name", "administrators", "owners"]
    utils.validate_fields(required_fields, request.json)
    role_title = " ".join(request.json.get("name").split())
    response = await roles_query.roles_search_duplicate(
        request.app.config.DB_CONN, role_title
    )
    if request.json.get("metadata") is None or request.json.get("metadata") == {}:
        set_metadata = {}
    else:
        set_metadata = request.json.get("metadata")
    set_metadata["sync_direction"] = "OUTBOUND"
    if not response:
        txn_key, txn_user_id = await utils.get_transactor_key(request)
        role_id = str(uuid4())
        batch_list = Role().batch_list(
            signer_keypair=txn_key,
            signer_user_id=txn_user_id,
            name=role_title,
            role_id=role_id,
            metadata=set_metadata,
            admins=request.json.get("administrators"),
            owners=request.json.get("owners"),
            description=request.json.get("description"),
        )
        sawtooth_response = await utils.send(
            request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT
        )

        if not sawtooth_response:
            LOGGER.warning("There was an error submitting the sawtooth transaction.")
            return await handle_errors(
                request,
                ApiInternalError(
                    "There was an error submitting the sawtooth transaction"
                ),
            )

        if role_title != "NextAdmins":
            distinguished_name_formatted = "CN=" + role_title + "," + GROUP_BASE_DN
            data_formatted = {
                "created_date": r.now(),
                "distinguished_name": distinguished_name_formatted,
                "group_nickname": role_title,
                "group_types": -2147483646,
                "name": role_title,
                "remote_id": distinguished_name_formatted,
            }

            env = Env()
            if env.int("ENABLE_LDAP_SYNC", 0):
                provider = env("LDAP_DC")
            elif env.int("ENABLE_AZURE_SYNC", 0):
                provider = env("TENANT_ID")
            else:
                provider = "NEXT-created"

            outbound_entry = {
                "data": data_formatted,
                "data_type": "group",
                "timestamp": r.now(),
                "provider_id": provider,
                "status": "UNCONFIRMED",
            }
            # Insert to outbound_queue and close
            await roles_query.insert_to_outboundqueue(
                request.app.config.DB_CONN, outbound_entry
            )
        else:
            LOGGER.info(
                "The role being created is NextAdmins, which is local to NEXT and will not be inserted into the outbound_queue."
            )
        return create_role_response(request, role_id)
    return await handle_errors(
        request,
        ApiTargetConflict(
            "Error: Could not create this role because the role name already exists."
        ),
    )
Beispiel #10
0
async def add_role_member(request, role_id):
    """Add a member to a role."""
    required_fields = ["id"]
    utils.validate_fields(required_fields, request.json)
    txn_key, txn_user_id = await utils.get_transactor_key(request)
    proposal_id = str(uuid4())
    approver = await fetch_relationships("role_owners", "role_id", role_id).run(
        request.app.config.DB_CONN
    )
    batch_list = Role().member.propose.batch_list(
        signer_keypair=txn_key,
        signer_user_id=txn_user_id,
        proposal_id=proposal_id,
        role_id=role_id,
        pack_id=request.json.get("pack_id"),
        next_id=request.json.get("id"),
        reason=request.json.get("reason"),
        metadata=request.json.get("metadata"),
        assigned_approver=approver,
    )
    batch_status = await utils.send(
        request.app.config.VAL_CONN,
        batch_list,
        request.app.config.TIMEOUT,
        request.json.get("tracker") and True,
    )
    role_resource = await roles_query.fetch_role_resource(
        request.app.config.DB_CONN, role_id
    )
    owners = role_resource.get("owners")
    requester_id = request.json.get("id")
    if requester_id in owners:
        is_proposal_ready = await wait_for_resource_in_db(
            "proposals", "proposal_id", proposal_id, max_attempts=30
        )
        if not is_proposal_ready:
            LOGGER.warning(
                "Max attempts exceeded. Proposal %s not found in RethinkDB.",
                proposal_id,
            )
            return await handle_errors(
                request,
                ApiInternalError(
                    "Max attempts exceeded. Proposal %s not found in RethinkDB."
                    % proposal_id
                ),
            )
        request.json["status"] = "APPROVED"
        request.json["reason"] = "I am the owner of this role"
        await proposals.update_proposal(request, proposal_id)
        if request.json.get("tracker"):
            events = {"batch_status": batch_status, "member_status": "MEMBER"}
            return utils.create_tracker_response(events)
        return json(
            {
                "message": "Owner is the requester. Proposal is autoapproved",
                "proposal_id": proposal_id,
            }
        )
    if request.json.get("tracker"):
        events = {"batch_status": batch_status}
        if batch_status == 1:
            events["member_status"] = "PENDING"
        return utils.create_tracker_response(events)
    return json({"proposal_id": proposal_id})
Beispiel #11
0
async def delete_role(request, role_id):
    """Delete a role by it's next_id.
    Args:
        role_id:
            str: the role_id field of the targeted role
    Returns:
        json:
            dict: {
                message:
                    str: the status of the role delete operation
                deleted:
                    int: count of the number of roles that were deleted
            }
    Raises:
        ApiForbidden:
            The user is not a system admin or owner of the targeted
            role.
        ApiNotFound:
            The role does not exist in RethinkDB.
        ApiInternalError:
            There was an error compiling blockchain transactions.
    """
    txn_key, txn_user_id = await utils.get_transactor_key(request)

    # does the role exist?
    if not await roles_query.does_role_exist(request.app.config.DB_CONN, role_id):
        LOGGER.warning(
            "Nonexistent Role – User %s is attempting to delete the nonexistent role %s",
            txn_user_id,
            role_id,
        )
        return await handle_not_found(
            request, ApiNotFound("The targeted role does not exist.")
        )

    is_role_owner = await check_role_owner_status(txn_user_id, role_id)
    if not is_role_owner:
        is_admin = await check_admin_status(txn_user_id)
        if not is_admin:
            LOGGER.warning(
                "Permission Denied – User %s does not have sufficient privilege to delete role %s.",
                txn_user_id,
                role_id,
            )
            return await handle_errors(
                request, ApiForbidden("You do not have permission to delete this role.")
            )

    txn_list = []
    txn_list = await create_rjct_ppsls_role_txns(
        txn_key, role_id, txn_user_id, txn_list
    )
    txn_list = await create_del_admin_by_role_txns(txn_key, role_id, txn_list)
    txn_list = await create_del_mmbr_by_role_txns(txn_key, role_id, txn_list)
    txn_list = await create_del_ownr_by_role_txns(txn_key, role_id, txn_list)
    txn_list = create_del_role_txns(txn_key, role_id, txn_list)

    # validate transaction list
    if not txn_list:
        LOGGER.warning(
            "txn_list is empty. There was an error processing the delete role transactions. Transaction list: %s",
            txn_list,
        )
        return await handle_errors(
            request,
            ApiInternalError(
                "An error occurred while creating the blockchain transactions to delete the role."
            ),
        )

    batch = batcher.make_batch_from_txns(transactions=txn_list, signer_keypair=txn_key)
    batch_list = batcher.batch_to_list(batch=batch)
    await utils.send(
        request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT
    )
    return json(
        {"message": "Role {} successfully deleted".format(role_id), "deleted": 1}
    )